home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / python-support / gnome-orca / orca / default.py < prev    next >
Encoding:
Python Source  |  2009-04-13  |  305.3 KB  |  7,825 lines

  1. # Orca
  2. #
  3. # Copyright 2004-2008 Sun Microsystems Inc.
  4. #
  5. # This library is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU Library General Public
  7. # License as published by the Free Software Foundation; either
  8. # version 2 of the License, or (at your option) any later version.
  9. #
  10. # This library is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  13. # Library General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU Library General Public
  16. # License along with this library; if not, write to the
  17. # Free Software Foundation, Inc., Franklin Street, Fifth Floor,
  18. # Boston MA  02110-1301 USA.
  19.  
  20. """The default Script for presenting information to the user using
  21. both speech and Braille.  This is based primarily on the de-facto
  22. standard implementation of the AT-SPI, which is the GAIL support
  23. for GTK."""
  24.  
  25. __id__        = "$Id: default.py 4675 2009-04-11 22:43:39Z wwalker $"
  26. __version__   = "$Revision: 4675 $"
  27. __date__      = "$Date: 2009-04-11 18:43:39 -0400 (Sat, 11 Apr 2009) $"
  28. __copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc."
  29. __license__   = "LGPL"
  30.  
  31. import locale
  32. import math
  33. import sys
  34. import time
  35.  
  36. import pyatspi
  37. import braille
  38. import chnames
  39. import debug
  40. import find
  41. import flat_review
  42. import input_event
  43. import keybindings
  44. import mag
  45. import outline
  46. import orca
  47. import orca_prefs
  48. import orca_state
  49. import phonnames
  50. import pronunciation_dict
  51. import punctuation_settings
  52. import rolenames
  53. import script
  54. import settings
  55. import speech
  56. import speechserver
  57. import mouse_review
  58. import text_attribute_names
  59.  
  60. from orca_i18n import _         # for gettext support
  61. from orca_i18n import ngettext  # for ngettext support
  62. from orca_i18n import C_        # to provide qualified translatable strings
  63.  
  64. ########################################################################
  65. #                                                                      #
  66. # The Default script class.                                            #
  67. #                                                                      #
  68. ########################################################################
  69.  
  70. class Script(script.Script):
  71.  
  72.     EMBEDDED_OBJECT_CHARACTER = u'\ufffc'
  73.     NO_BREAK_SPACE_CHARACTER  = u'\u00a0'
  74.  
  75.     def __init__(self, app):
  76.         """Creates a new script for the given application.
  77.  
  78.         Arguments:
  79.         - app: the application to create a script for.
  80.         """
  81.         script.Script.__init__(self, app)
  82.  
  83.         self.flatReviewContext  = None
  84.         self.windowActivateTime = None
  85.         self.lastReviewCurrentEvent = None
  86.         self.exitLearnModeKeyBinding = None
  87.         self.targetCursorCell = None
  88.  
  89.         self.justEnteredFlatReviewMode = False
  90.  
  91.         self.digits = '0123456789'
  92.         self.whitespace = ' \t\n\r\v\f'
  93.  
  94.         # Used to determine whether the used double clicked on the
  95.         # "where am I" key.
  96.         #
  97.         self.lastWhereAmIEvent = None
  98.  
  99.         # Used to determine whether the used double clicked on the
  100.         # "say all" key.
  101.         #
  102.         self.lastSayAllEvent = None
  103.  
  104.         # Unicode currency symbols (populated by the
  105.         # getUnicodeCurrencySymbols() routine).
  106.         #
  107.         self._unicodeCurrencySymbols = []
  108.  
  109.         # Used by the visualAppearanceChanged routine for updating whether
  110.         # progress bars are spoken.
  111.         #
  112.         self.lastProgressBarTime = {}
  113.         self.lastProgressBarValue = {}
  114.  
  115.         self.lastSelectedMenu = None
  116.  
  117.         # A dictionary of non-standardly-named text attributes and their
  118.         # Atk equivalents.
  119.         #
  120.         self.attributeNamesDict = {}
  121.  
  122.     def setupInputEventHandlers(self):
  123.         """Defines InputEventHandler fields for this script that can be
  124.         called by the key and braille bindings."""
  125.  
  126.         self.inputEventHandlers["leftClickReviewItemHandler"] = \
  127.             input_event.InputEventHandler(
  128.                 Script.leftClickReviewItem,
  129.                 # Translators: the 'flat review' feature of Orca
  130.                 # allows the blind user to explore the text in a
  131.                 # window in a 2D fashion.  That is, Orca treats all
  132.                 # the text from all objects in a window (e.g.,
  133.                 # buttons, labels, etc.) as a sequence of words in a
  134.                 # sequence of lines.  The flat review feature allows
  135.                 # the user to explore this text by the {previous,next}
  136.                 # {line,word,character}.  A left click means to generate
  137.                 # a left mouse button click on the current item.
  138.                 #
  139.                 _("Performs left click on current flat review item."))
  140.  
  141.         self.inputEventHandlers["rightClickReviewItemHandler"] = \
  142.              input_event.InputEventHandler(
  143.                 Script.rightClickReviewItem,
  144.                 # Translators: the 'flat review' feature of Orca
  145.                 # allows the blind user to explore the text in a
  146.                 # window in a 2D fashion.  That is, Orca treats all
  147.                 # the text from all objects in a window (e.g.,
  148.                 # buttons, labels, etc.) as a sequence of words in a
  149.                 # sequence of lines.  The flat review feature allows
  150.                 # the user to explore this text by the {previous,next}
  151.                 # {line,word,character}.  A right click means to generate
  152.                 # a right mouse button click on the current item.
  153.                 #
  154.                 _("Performs right click on current flat review item."))
  155.  
  156.         self.inputEventHandlers["sayAllHandler"] = \
  157.             input_event.InputEventHandler(
  158.                 Script.sayAll,
  159.                 # Translators: the Orca "SayAll" command allows the
  160.                 # user to press a key and have the entire document in
  161.                 # a window be automatically spoken to the user.  If
  162.                 # the user presses any key during a SayAll operation,
  163.                 # the speech will be interrupted and the cursor will
  164.                 # be positioned at the point where the speech was
  165.                 # interrupted.
  166.                 #
  167.                 _("Speaks entire document."))
  168.  
  169.         self.inputEventHandlers["whereAmIBasicHandler"] = \
  170.             input_event.InputEventHandler(
  171.                 Script.whereAmIBasic,
  172.                 # Translators: the "Where am I" feature of Orca allows
  173.                 # a user to press a key and then have information
  174.                 # about their current context spoken and brailled to
  175.                 # them.  For example, the information may include the
  176.                 # name of the current pushbutton with focus as well as
  177.                 # its mnemonic.
  178.                 #
  179.                 _("Performs the basic where am I operation."))
  180.  
  181.         self.inputEventHandlers["whereAmIDetailedHandler"] = \
  182.             input_event.InputEventHandler(
  183.                 Script.whereAmIDetailed,
  184.                 # Translators: the "Where am I" feature of Orca allows
  185.                 # a user to press a key and then have information
  186.                 # about their current context spoken and brailled to
  187.                 # them.  For example, the information may include the
  188.                 # name of the current pushbutton with focus as well as
  189.                 # its mnemonic.
  190.                 #
  191.                 _("Performs the detailed where am I operation."))
  192.  
  193.         self.inputEventHandlers["getTitleHandler"] = \
  194.             input_event.InputEventHandler(
  195.                 Script.getTitle,
  196.                 # Translators: This command will cause the window's
  197.                 # title to be spoken.
  198.                 #
  199.                 _("Speaks the title bar."))
  200.  
  201.         self.inputEventHandlers["getStatusBarHandler"] = \
  202.             input_event.InputEventHandler(
  203.                 Script.getStatusBar,
  204.                 # Translators: This command will cause the window's
  205.                 # status bar contents to be spoken.
  206.                 #
  207.                 _("Speaks the status bar."))
  208.  
  209.         self.inputEventHandlers["findHandler"] = \
  210.             input_event.InputEventHandler(
  211.                 orca.showFindGUI,
  212.                 # Translators: the Orca "Find" dialog allows a user to
  213.                 # search for text in a window and then move focus to
  214.                 # that text.  For example, they may want to find the
  215.                 # "OK" button.
  216.                 #
  217.                 _("Opens the Orca Find dialog."))
  218.  
  219.         self.inputEventHandlers["findNextHandler"] = \
  220.             input_event.InputEventHandler(
  221.                 Script.findNext,
  222.                 # Translators: the Orca "Find" dialog allows a user to
  223.                 # search for text in a window and then move focus to
  224.                 # that text.  For example, they may want to find the
  225.                 # "OK" button.  This string is used for finding the
  226.                 # next occurence of a string.
  227.                 #
  228.                 _("Searches for the next instance of a string."))
  229.  
  230.         self.inputEventHandlers["findPreviousHandler"] = \
  231.             input_event.InputEventHandler(
  232.                 Script.findPrevious,
  233.                 # Translators: the Orca "Find" dialog allows a user to
  234.                 # search for text in a window and then move focus to
  235.                 # that text.  For example, they may want to find the
  236.                 # "OK" button.  This string is used for finding the
  237.                 # previous occurence of a string.
  238.                 #
  239.                 _("Searches for the previous instance of a string."))
  240.  
  241.         self.inputEventHandlers["showZonesHandler"] = \
  242.             input_event.InputEventHandler(
  243.                 Script.showZones,
  244.                 # Translators: this is a debug message that Orca users
  245.                 # will not normally see. It describes a debug routine that
  246.                 # paints rectangles around the interesting (e.g., text)
  247.                 # zones in the active window for the application that
  248.                 # currently has focus.
  249.                 #
  250.                 _("Paints and prints the visible zones in the active window."))
  251.  
  252.         self.inputEventHandlers["toggleFlatReviewModeHandler"] = \
  253.             input_event.InputEventHandler(
  254.                 Script.toggleFlatReviewMode,
  255.                 # Translators: the 'flat review' feature of Orca
  256.                 # allows the blind user to explore the text in a
  257.                 # window in a 2D fashion.  That is, Orca treats all
  258.                 # the text from all objects in a window (e.g.,
  259.                 # buttons, labels, etc.) as a sequence of words in a
  260.                 # sequence of lines.  The flat review feature allows
  261.                 # the user to explore this text by the {previous,next}
  262.                 # {line,word,character}.
  263.                 #
  264.                 _("Enters and exits flat review mode."))
  265.  
  266.         self.inputEventHandlers["reviewPreviousLineHandler"] = \
  267.             input_event.InputEventHandler(
  268.                 Script.reviewPreviousLine,
  269.                 # Translators: the 'flat review' feature of Orca
  270.                 # allows the blind user to explore the text in a
  271.                 # window in a 2D fashion.  That is, Orca treats all
  272.                 # the text from all objects in a window (e.g.,
  273.                 # buttons, labels, etc.) as a sequence of words in a
  274.                 # sequence of lines.  The flat review feature allows
  275.                 # the user to explore this text by the {previous,next}
  276.                 # {line,word,character}.
  277.                 #
  278.                 _("Moves flat review to the beginning of the previous line."))
  279.  
  280.         self.inputEventHandlers["reviewHomeHandler"] = \
  281.             input_event.InputEventHandler(
  282.                 Script.reviewHome,
  283.                 # Translators: the 'flat review' feature of Orca
  284.                 # allows the blind user to explore the text in a
  285.                 # window in a 2D fashion.  That is, Orca treats all
  286.                 # the text from all objects in a window (e.g.,
  287.                 # buttons, labels, etc.) as a sequence of words in a
  288.                 # sequence of lines.  The flat review feature allows
  289.                 # the user to explore this text by the {previous,next}
  290.                 # {line,word,character}.  The home position is the
  291.                 # beginning of the content in the window.
  292.                 #
  293.                 _("Moves flat review to the home position."))
  294.  
  295.         self.inputEventHandlers["reviewCurrentLineHandler"] = \
  296.             input_event.InputEventHandler(
  297.                 Script.reviewCurrentLine,
  298.                 # Translators: the 'flat review' feature of Orca
  299.                 # allows the blind user to explore the text in a
  300.                 # window in a 2D fashion.  That is, Orca treats all
  301.                 # the text from all objects in a window (e.g.,
  302.                 # buttons, labels, etc.) as a sequence of words in a
  303.                 # sequence of lines.  The flat review feature allows
  304.                 # the user to explore this text by the {previous,next}
  305.                 # {line,word,character}.  This particular command will
  306.                 # cause Orca to speak the current line.
  307.                 #
  308.                 _("Speaks the current flat review line."))
  309.  
  310.         self.inputEventHandlers["reviewSpellCurrentLineHandler"] = \
  311.             input_event.InputEventHandler(
  312.                 Script.reviewSpellCurrentLine,
  313.                 # Translators: the 'flat review' feature of Orca
  314.                 # allows the blind user to explore the text in a
  315.                 # window in a 2D fashion.  That is, Orca treats all
  316.                 # the text from all objects in a window (e.g.,
  317.                 # buttons, labels, etc.) as a sequence of words in a
  318.                 # sequence of lines.  The flat review feature allows
  319.                 # the user to explore this text by the {previous,next}
  320.                 # {line,word,character}. This particular command will
  321.                 # cause Orca to spell the current line.
  322.                 #
  323.                 _("Spells the current flat review line."))
  324.  
  325.         self.inputEventHandlers["reviewPhoneticCurrentLineHandler"] = \
  326.             input_event.InputEventHandler(
  327.                 Script.reviewPhoneticCurrentLine,
  328.                 # Translators: the 'flat review' feature of Orca
  329.                 # allows the blind user to explore the text in a
  330.                 # window in a 2D fashion.  That is, Orca treats all
  331.                 # the text from all objects in a window (e.g.,
  332.                 # buttons, labels, etc.) as a sequence of words in a
  333.                 # sequence of lines.  The flat review feature allows
  334.                 # the user to explore this text by the {previous,next}
  335.                 # {line,word,character}. This particular command will
  336.                 # cause Orca to "phonetically spell" the current line,
  337.                 # saying "Alpha" for "a", "Bravo" for "b" and so on.
  338.                 #
  339.                 _("Phonetically spells the current flat review line."))
  340.  
  341.         self.inputEventHandlers["reviewNextLineHandler"] = \
  342.             input_event.InputEventHandler(
  343.                 Script.reviewNextLine,
  344.                 # Translators: the 'flat review' feature of Orca
  345.                 # allows the blind user to explore the text in a
  346.                 # window in a 2D fashion.  That is, Orca treats all
  347.                 # the text from all objects in a window (e.g.,
  348.                 # buttons, labels, etc.) as a sequence of words in a
  349.                 # sequence of lines.  The flat review feature allows
  350.                 # the user to explore this text by the {previous,next}
  351.                 # {line,word,character}.
  352.                 #
  353.                 _("Moves flat review to the beginning of the next line."))
  354.  
  355.         self.inputEventHandlers["reviewEndHandler"] = \
  356.             input_event.InputEventHandler(
  357.                 Script.reviewEnd,
  358.                 # Translators: the 'flat review' feature of Orca
  359.                 # allows the blind user to explore the text in a
  360.                 # window in a 2D fashion.  That is, Orca treats all
  361.                 # the text from all objects in a window (e.g.,
  362.                 # buttons, labels, etc.) as a sequence of words in a
  363.                 # sequence of lines.  The flat review feature allows
  364.                 # the user to explore this text by the {previous,next}
  365.                 # {line,word,character}.  The end position is the last
  366.                 # bit of information in the window.
  367.                 #
  368.                 _("Moves flat review to the end position."))
  369.  
  370.         self.inputEventHandlers["reviewPreviousItemHandler"] = \
  371.             input_event.InputEventHandler(
  372.                 Script.reviewPreviousItem,
  373.                 # Translators: the 'flat review' feature of Orca
  374.                 # allows the blind user to explore the text in a
  375.                 # window in a 2D fashion.  That is, Orca treats all
  376.                 # the text from all objects in a window (e.g.,
  377.                 # buttons, labels, etc.) as a sequence of words in a
  378.                 # sequence of lines.  The flat review feature allows
  379.                 # the user to explore this text by the {previous,next}
  380.                 # {line,word,character}.  Previous will go backwards
  381.                 # in the window until you reach the top (i.e., it will
  382.                 # wrap across lines if necessary).
  383.                 #
  384.                 _("Moves flat review to the previous item or word."))
  385.  
  386.         self.inputEventHandlers["reviewAboveHandler"] = \
  387.             input_event.InputEventHandler(
  388.                 Script.reviewAbove,
  389.                 # Translators: the 'flat review' feature of Orca
  390.                 # allows the blind user to explore the text in a
  391.                 # window in a 2D fashion.  That is, Orca treats all
  392.                 # the text from all objects in a window (e.g.,
  393.                 # buttons, labels, etc.) as a sequence of words in a
  394.                 # sequence of lines.  The flat review feature allows
  395.                 # the user to explore this text by the {previous,next}
  396.                 # {line,word,character}.  Above in this case means
  397.                 # geographically above, as if you drew a vertical line
  398.                 # in the window.
  399.                 #
  400.                 _("Moves flat review to the word above the current word."))
  401.  
  402.         self.inputEventHandlers["reviewCurrentItemHandler"] = \
  403.             input_event.InputEventHandler(
  404.                 Script.reviewCurrentItem,
  405.                 # Translators: the 'flat review' feature of Orca
  406.                 # allows the blind user to explore the text in a
  407.                 # window in a 2D fashion.  That is, Orca treats all
  408.                 # the text from all objects in a window (e.g.,
  409.                 # buttons, labels, etc.) as a sequence of words in a
  410.                 # sequence of lines.  The flat review feature allows
  411.                 # the user to explore this text by the {previous,next}
  412.                 # {line,word,character}.  This command will speak the
  413.                 # current word or item.
  414.                 #
  415.                 _("Speaks the current flat review item or word."))
  416.  
  417.         self.inputEventHandlers["reviewSpellCurrentItemHandler"] = \
  418.             input_event.InputEventHandler(
  419.                 Script.reviewSpellCurrentItem,
  420.                 # Translators: the 'flat review' feature of Orca
  421.                 # allows the blind user to explore the text in a
  422.                 # window in a 2D fashion.  That is, Orca treats all
  423.                 # the text from all objects in a window (e.g.,
  424.                 # buttons, labels, etc.) as a sequence of words in a
  425.                 # sequence of lines.  The flat review feature allows
  426.                 # the user to explore this text by the {previous,next}
  427.                 # {line,word,character}.  This command will spell out
  428.                 # the current word or item letter by letter.
  429.                 #
  430.                 _("Spells the current flat review item or word."))
  431.  
  432.         self.inputEventHandlers["reviewPhoneticCurrentItemHandler"] = \
  433.             input_event.InputEventHandler(
  434.                 Script.reviewPhoneticCurrentItem,
  435.                 # Translators: the 'flat review' feature of Orca
  436.                 # allows the blind user to explore the text in a
  437.                 # window in a 2D fashion.  That is, Orca treats all
  438.                 # the text from all objects in a window (e.g.,
  439.                 # buttons, labels, etc.) as a sequence of words in a
  440.                 # sequence of lines.  The flat review feature allows
  441.                 # the user to explore this text by the {previous,next}
  442.                 # {line,word,character}.  This command will spell out
  443.                 # the current word or item phonetically, saying "Alpha"
  444.                 # for "a", "Bravo" for "b" and so on.
  445.                 #
  446.                 _("Phonetically spells the current flat review item or word."))
  447.  
  448.         self.inputEventHandlers["reviewCurrentAccessibleHandler"] = \
  449.             input_event.InputEventHandler(
  450.                 Script.reviewCurrentAccessible,
  451.                 # Translators: the 'flat review' feature of Orca
  452.                 # allows the blind user to explore the text in a
  453.                 # window in a 2D fashion.  That is, Orca treats all
  454.                 # the text from all objects in a window (e.g.,
  455.                 # buttons, labels, etc.) as a sequence of words in a
  456.                 # sequence of lines.  The flat review feature allows
  457.                 # the user to explore this text by the {previous,next}
  458.                 # {line,word,character}.  The flat review object is
  459.                 # typically something like a pushbutton, a label, or
  460.                 # some other GUI widget.  The 'speaks' means it will
  461.                 # speak the text associated with the object.
  462.                 #
  463.                 _("Speaks the current flat review object."))
  464.  
  465.         self.inputEventHandlers["reviewNextItemHandler"] = \
  466.             input_event.InputEventHandler(
  467.                 Script.reviewNextItem,
  468.                 # Translators: the 'flat review' feature of Orca
  469.                 # allows the blind user to explore the text in a
  470.                 # window in a 2D fashion.  That is, Orca treats all
  471.                 # the text from all objects in a window (e.g.,
  472.                 # buttons, labels, etc.) as a sequence of words in a
  473.                 # sequence of lines.  The flat review feature allows
  474.                 # the user to explore this text by the {previous,next}
  475.                 # {line,word,character}.  Next will go forwards
  476.                 # in the window until you reach the end (i.e., it will
  477.                 # wrap across lines if necessary).
  478.                 #
  479.                 _("Moves flat review to the next item or word."))
  480.  
  481.         self.inputEventHandlers["reviewBelowHandler"] = \
  482.             input_event.InputEventHandler(
  483.                 Script.reviewBelow,
  484.                 # Translators: the 'flat review' feature of Orca
  485.                 # allows the blind user to explore the text in a
  486.                 # window in a 2D fashion.  That is, Orca treats all
  487.                 # the text from all objects in a window (e.g.,
  488.                 # buttons, labels, etc.) as a sequence of words in a
  489.                 # sequence of lines.  The flat review feature allows
  490.                 # the user to explore this text by the {previous,next}
  491.                 # {line,word,character}.  Below in this case means
  492.                 # geographically below, as if you drew a vertical line
  493.                 # downward on the screen.
  494.                 #
  495.                 _("Moves flat review to the word below the current word."))
  496.  
  497.         self.inputEventHandlers["reviewPreviousCharacterHandler"] = \
  498.             input_event.InputEventHandler(
  499.                 Script.reviewPreviousCharacter,
  500.                 # Translators: the 'flat review' feature of Orca
  501.                 # allows the blind user to explore the text in a
  502.                 # window in a 2D fashion.  That is, Orca treats all
  503.                 # the text from all objects in a window (e.g.,
  504.                 # buttons, labels, etc.) as a sequence of words in a
  505.                 # sequence of lines.  The flat review feature allows
  506.                 # the user to explore this text by the {previous,next}
  507.                 # {line,word,character}.  Previous will go backwards
  508.                 # in the window until you reach the top (i.e., it will
  509.                 # wrap across lines if necessary).
  510.                 #
  511.                 _("Moves flat review to the previous character."))
  512.  
  513.         self.inputEventHandlers["reviewEndOfLineHandler"] = \
  514.             input_event.InputEventHandler(
  515.                 Script.reviewEndOfLine,
  516.                 # Translators: the 'flat review' feature of Orca
  517.                 # allows the blind user to explore the text in a
  518.                 # window in a 2D fashion.  That is, Orca treats all
  519.                 # the text from all objects in a window (e.g.,
  520.                 # buttons, labels, etc.) as a sequence of words in a
  521.                 # sequence of lines.  The flat review feature allows
  522.                 # the user to explore this text by the {previous,next}
  523.                 # {line,word,character}.
  524.                 #
  525.                 _("Moves flat review to the end of the line."))
  526.  
  527.         self.inputEventHandlers["reviewCurrentCharacterHandler"] = \
  528.             input_event.InputEventHandler(
  529.                 Script.reviewCurrentCharacter,
  530.                 # Translators: the 'flat review' feature of Orca
  531.                 # allows the blind user to explore the text in a
  532.                 # window in a 2D fashion.  That is, Orca treats all
  533.                 # the text from all objects in a window (e.g.,
  534.                 # buttons, labels, etc.) as a sequence of words in a
  535.                 # sequence of lines.  The flat review feature allows
  536.                 # the user to explore this text by the {previous,next}
  537.                 # {line,word,character}.  Previous will go backwards
  538.                 # in the window until you reach the top (i.e., it will
  539.                 # wrap across lines if necessary).  The 'speaks' in
  540.                 # this case will be the spoken language form of the
  541.                 # character currently being reviewed.
  542.                 #
  543.                 _("Speaks the current flat review character."))
  544.  
  545.         self.inputEventHandlers["reviewSpellCurrentCharacterHandler"] = \
  546.             input_event.InputEventHandler(
  547.                 Script.reviewSpellCurrentCharacter,
  548.                 # Translators: the 'flat review' feature of Orca
  549.                 # allows the blind user to explore the text in a
  550.                 # window in a 2D fashion.  That is, Orca treats all
  551.                 # the text from all objects in a window (e.g.,
  552.                 # buttons, labels, etc.) as a sequence of words in a
  553.                 # sequence of lines.  The flat review feature allows
  554.                 # the user to explore this text by the {previous,next}
  555.                 # {line,word,character}.  Previous will go backwards
  556.                 # in the window until you reach the top (i.e., it will
  557.                 # wrap across lines if necessary).  This command will
  558.                 # cause Orca to speak a phonetic representation of the
  559.                 # character currently being reviewed, saying "Alpha"
  560.                 # for "a", "Bravo" for "b" and so on.
  561.                 #
  562.                 _("Phonetically speaks the current flat review character."))
  563.  
  564.         self.inputEventHandlers["reviewNextCharacterHandler"] = \
  565.             input_event.InputEventHandler(
  566.                 Script.reviewNextCharacter,
  567.                 # Translators: the 'flat review' feature of Orca
  568.                 # allows the blind user to explore the text in a
  569.                 # window in a 2D fashion.  That is, Orca treats all
  570.                 # the text from all objects in a window (e.g.,
  571.                 # buttons, labels, etc.) as a sequence of words in a
  572.                 # sequence of lines.  The flat review feature allows
  573.                 # the user to explore this text by the {previous,next}
  574.                 # {line,word,character}.  Next will go forwards
  575.                 # in the window until you reach the end (i.e., it will
  576.                 # wrap across lines if necessary).
  577.                 #
  578.                 _("Moves flat review to the next character."))
  579.  
  580.         self.inputEventHandlers["toggleTableCellReadModeHandler"] = \
  581.             input_event.InputEventHandler(
  582.                 Script.toggleTableCellReadMode,
  583.                 # Translators: when users are navigating a table, they
  584.                 # sometimes want the entire row of a table read, or
  585.                 # they just want the current cell to be presented to them.
  586.                 #
  587.                 _("Toggles whether to read just the current table cell " \
  588.                   "or the whole row."))
  589.  
  590.         self.inputEventHandlers["readCharAttributesHandler"] = \
  591.             input_event.InputEventHandler(
  592.                 Script.readCharAttributes,
  593.                 # Translators: the attributes being presented are the
  594.                 # text attributes, such as bold, italic, font name,
  595.                 # font size, etc.
  596.                 #
  597.                 _("Reads the attributes associated with the current text " \
  598.                   "character."))
  599.  
  600.         self.inputEventHandlers["reportScriptInfoHandler"] = \
  601.             input_event.InputEventHandler(
  602.                 Script.reportScriptInfo,
  603.                 # Translators: this is a debug message that Orca users
  604.                 # will not normally see. It describes a debug routine
  605.                 # that outputs useful information on the current script
  606.                 #  via speech and braille. This information will be
  607.                 # helpful to script writers.
  608.                 #
  609.                 _("Reports information on current script."))
  610.  
  611.         self.inputEventHandlers["panBrailleLeftHandler"] = \
  612.             input_event.InputEventHandler(
  613.                 Script.panBrailleLeft,
  614.                 # Translators: a refreshable braille display is an
  615.                 # external hardware device that presents braille
  616.                 # character to the user.  There are a limited number
  617.                 # of cells on the display (typically 40 cells).  Orca
  618.                 # provides the feature to build up a longer logical
  619.                 # line and allow the user to press buttons on the
  620.                 # braille display so they can pan left and right over
  621.                 # this line.
  622.                 #
  623.                 _("Pans the braille display to the left."),
  624.                 False) # Do not enable learn mode for this action
  625.  
  626.         self.inputEventHandlers["panBrailleRightHandler"] = \
  627.             input_event.InputEventHandler(
  628.                 Script.panBrailleRight,
  629.                 # Translators: a refreshable braille display is an
  630.                 # external hardware device that presents braille
  631.                 # character to the user.  There are a limited number
  632.                 # of cells on the display (typically 40 cells).  Orca
  633.                 # provides the feature to build up a longer logical
  634.                 # line and allow the user to press buttons on the
  635.                 # braille display so they can pan left and right over
  636.                 # this line.
  637.                 #
  638.                 _("Pans the braille display to the right."),
  639.                 False) # Do not enable learn mode for this action
  640.  
  641.         self.inputEventHandlers["reviewBottomLeftHandler"] = \
  642.             input_event.InputEventHandler(
  643.                 Script.reviewBottomLeft,
  644.                 # Translators: the 'flat review' feature of Orca
  645.                 # allows the blind user to explore the text in a
  646.                 # window in a 2D fashion.  That is, Orca treats all
  647.                 # the text from all objects in a window (e.g.,
  648.                 # buttons, labels, etc.) as a sequence of words in a
  649.                 # sequence of lines.  The flat review feature allows
  650.                 # the user to explore this text by the {previous,next}
  651.                 # {line,word,character}.  The bottom left is the bottom
  652.                 # left of the window currently being reviewed.
  653.                 #
  654.                 _("Moves flat review to the bottom left."))
  655.  
  656.         self.inputEventHandlers["goBrailleHomeHandler"] = \
  657.             input_event.InputEventHandler(
  658.                 Script.goBrailleHome,
  659.                 # Translators: the 'flat review' feature of Orca
  660.                 # allows the blind user to explore the text in a
  661.                 # window in a 2D fashion.  That is, Orca treats all
  662.                 # the text from all objects in a window (e.g.,
  663.                 # buttons, labels, etc.) as a sequence of words in a
  664.                 # sequence of lines.  The flat review feature allows
  665.                 # the user to explore this text by the {previous,next}
  666.                 # {line,word,character}.  Flat review is modal, and
  667.                 # the user can be exploring the window without changing
  668.                 # which object in the window which has focus.  The
  669.                 # feature used here will return the flat review to the
  670.                 # object with focus.
  671.                 #
  672.                 _("Returns to object with keyboard focus."))
  673.  
  674.         self.inputEventHandlers["enterLearnModeHandler"] = \
  675.             input_event.InputEventHandler(
  676.                 Script.enterLearnMode,
  677.                 # Translators: Orca has a "Learn Mode" that will allow
  678.                 # the user to type any key on the keyboard and hear what
  679.                 # the effects of that key would be.  The effects might
  680.                 # be what Orca would do if it had a handler for the
  681.                 # particular key combination, or they might just be to
  682.                 # echo the name of the key if Orca doesn't have a handler.
  683.                 #
  684.                 _("Enters learn mode.  Press escape to exit learn mode."))
  685.  
  686.         self.inputEventHandlers["decreaseSpeechRateHandler"] = \
  687.             input_event.InputEventHandler(
  688.                 speech.decreaseSpeechRate,
  689.                 # Translators: the speech rate is how fast the speech
  690.                 # synthesis engine will generate speech.
  691.                 #
  692.                 _("Decreases the speech rate."))
  693.  
  694.         self.inputEventHandlers["increaseSpeechRateHandler"] = \
  695.             input_event.InputEventHandler(
  696.                 speech.increaseSpeechRate,
  697.                 # Translators: the speech rate is how fast the speech
  698.                 # synthesis engine will generate speech.
  699.                 #
  700.                 _("Increases the speech rate."))
  701.  
  702.         self.inputEventHandlers["decreaseSpeechPitchHandler"] = \
  703.             input_event.InputEventHandler(
  704.                 speech.decreaseSpeechPitch,
  705.                 # Translators: the speech pitch is how high or low in
  706.                 # pitch/frequency the speech synthesis engine will
  707.                 # generate speech.
  708.                 #
  709.                 _("Decreases the speech pitch."))
  710.  
  711.         self.inputEventHandlers["increaseSpeechPitchHandler"] = \
  712.             input_event.InputEventHandler(
  713.                 speech.increaseSpeechPitch,
  714.                 # Translators: the speech pitch is how high or low in
  715.                 # pitch/frequency the speech synthesis engine will
  716.                 # generate speech.
  717.                 #
  718.                 _("Increases the speech pitch."))
  719.  
  720.         self.inputEventHandlers["shutdownHandler"] = \
  721.             input_event.InputEventHandler(
  722.                 orca.quitOrca,
  723.                 _("Quits Orca"))
  724.  
  725.         self.inputEventHandlers["preferencesSettingsHandler"] = \
  726.             input_event.InputEventHandler(
  727.                 orca.showPreferencesGUI,
  728.                 # Translators: the preferences configuration dialog is
  729.                 # the dialog that allows users to set their preferences
  730.                 # for Orca.
  731.                 #
  732.                 _("Displays the preferences configuration dialog."))
  733.  
  734.         self.inputEventHandlers["appPreferencesSettingsHandler"] = \
  735.             input_event.InputEventHandler(
  736.                 orca.showAppPreferencesGUI,
  737.                 # Translators: the application preferences configuration
  738.                 # dialog is the dialog that allows users to set their
  739.                 # preferences for a specific application within Orca.
  740.                 #
  741.                 _("Displays the application preferences configuration dialog."))
  742.  
  743.         self.inputEventHandlers["toggleSilenceSpeechHandler"] = \
  744.             input_event.InputEventHandler(
  745.                 orca.toggleSilenceSpeech,
  746.                 # Translators: Orca allows the user to turn speech synthesis
  747.                 # on or off.  We call it 'silencing'.
  748.                 #
  749.                 _("Toggles the silencing of speech."))
  750.  
  751.         self.inputEventHandlers["listAppsHandler"] = \
  752.             input_event.InputEventHandler(
  753.                 Script.printAppsHandler,
  754.                 # Translators: this is a debug message that Orca users
  755.                 # will not normally see. It describes a debug routine
  756.                 # that prints a list of all known applications currently
  757.                 # running on the desktop, to stdout.
  758.                 #
  759.                 _("Prints a debug listing of all known applications to the " \
  760.                 "console where Orca is running."))
  761.  
  762.         self.inputEventHandlers["cycleDebugLevelHandler"] = \
  763.             input_event.InputEventHandler(
  764.                 orca.cycleDebugLevel,
  765.                 # Translators: this is a debug message that Orca users
  766.                 # will not normally see. It describes a debug routine
  767.                 # that allows the user to adjust the level of debug
  768.                 # information that Orca generates at run time.
  769.                 #
  770.                 _("Cycles the debug level at run time."))
  771.  
  772.         self.inputEventHandlers["printActiveAppHandler"] = \
  773.             input_event.InputEventHandler(
  774.                 Script.printActiveAppHandler,
  775.                 # Translators: this is a debug message that Orca users
  776.                 # will not normally see. It describes a debug routine
  777.                 # that prints useful debugging information to the console,
  778.                 # for the application that is currently running (has focus).
  779.                 #
  780.                 _("Prints debug information about the currently active " \
  781.                 "application to the console where Orca is running."))
  782.  
  783.         self.inputEventHandlers["printAncestryHandler"] = \
  784.             input_event.InputEventHandler(
  785.                 Script.printAncestryHandler,
  786.                 # Translators: this is a debug message that Orca users
  787.                 # will not normally see. It describes a debug routine
  788.                 # that will take the component in the currently running
  789.                 # application that has focus, and print debug information
  790.                 # to the console giving its component ancestry (i.e. all
  791.                 # the components that are its descendants in the component
  792.                 # tree).
  793.                 #
  794.                 _("Prints debug information about the ancestry of the object " \
  795.                 "with focus."))
  796.  
  797.         self.inputEventHandlers["printHierarchyHandler"] = \
  798.             input_event.InputEventHandler(
  799.                 Script.printHierarchyHandler,
  800.                 # Translators: this is a debug message that Orca users
  801.                 # will not normally see. It describes a debug routine
  802.                 # that will take the currently running application, and
  803.                 # print debug information to the console giving its
  804.                 # component hierarchy (i.e. all the components and all
  805.                 # their descendants in the component tree).
  806.                 #
  807.                 _("Prints debug information about the application with focus."))
  808.  
  809.         self.inputEventHandlers["printMemoryUsageHandler"] = \
  810.             input_event.InputEventHandler(
  811.                 Script.printMemoryUsageHandler,
  812.                 # Translators: this is a debug message that Orca users
  813.                 # will not normally see. It describes a debug routine
  814.                 # that will print Orca memory usage information.
  815.                 #
  816.                 _("Prints memory usage information."))
  817.  
  818.         self.inputEventHandlers["bookmarkCurrentWhereAmI"] = \
  819.             input_event.InputEventHandler(
  820.                 Script.bookmarkCurrentWhereAmI,
  821.                 # Translators: this command announces information regarding
  822.                 # the relationship of the given bookmark to the current
  823.                 # position
  824.                 #
  825.                 _("Bookmark where am I with respect to current position."))
  826.  
  827.         self.inputEventHandlers["goToBookmark"] = \
  828.             input_event.InputEventHandler(
  829.                 Script.goToBookmark,
  830.                 # Translators: this command moves the current position to the
  831.                 # location stored at the bookmark.
  832.                 #
  833.                 _("Go to bookmark."))
  834.  
  835.         self.inputEventHandlers["addBookmark"] = \
  836.             input_event.InputEventHandler(
  837.                 Script.addBookmark,
  838.                 # Translators: this event handler binds an in-page accessible
  839.                 # object location to the given input key command.
  840.                 #
  841.                 _("Add bookmark."))
  842.  
  843.         self.inputEventHandlers["saveBookmarks"] = \
  844.             input_event.InputEventHandler(
  845.                 Script.saveBookmarks,
  846.                 # Translators: this event handler saves all bookmarks for the
  847.                 # current application to disk.
  848.                 #
  849.                 _("Save bookmarks."))
  850.  
  851.         self.inputEventHandlers["goToNextBookmark"] = \
  852.             input_event.InputEventHandler(
  853.                 Script.goToNextBookmark,
  854.                 # Translators: this event handler cycles through the registered
  855.                 # bookmarks and takes the user to the next bookmark location.
  856.                 #
  857.                 _("Go to next bookmark location."))
  858.  
  859.         self.inputEventHandlers["goToPrevBookmark"] = \
  860.             input_event.InputEventHandler(
  861.                 Script.goToPrevBookmark,
  862.                 # Translators: this event handler cycles through the
  863.                 # registered bookmarks and takes the user to the previous
  864.                 # bookmark location.
  865.                 #
  866.                 _("Go to previous bookmark location."))
  867.  
  868.         self.inputEventHandlers["toggleColorEnhancementsHandler"] = \
  869.             input_event.InputEventHandler(
  870.                 mag.toggleColorEnhancements,
  871.                 # Translators: "color enhancements" are changes users can
  872.                 # make to the appearance of the screen to make things easier
  873.                 # to see, such as inverting the colors or applying a tint.
  874.                 # This command toggles these enhancements on/off.
  875.                 #
  876.                 _("Toggles color enhancements."))
  877.  
  878.         self.inputEventHandlers["toggleMouseEnhancementsHandler"] = \
  879.             input_event.InputEventHandler(
  880.                 mag.toggleMouseEnhancements,
  881.                 # Translators: "mouse enhancements" are changes users can
  882.                 # make to the appearance of the mouse pointer to make it
  883.                 # easier to see, such as increasing its size, changing its
  884.                 # color, and surrounding it with crosshairs.  This command
  885.                 # toggles these enhancements on/off.
  886.                 #
  887.                 _("Toggles mouse enhancements."))
  888.  
  889.         self.inputEventHandlers["increaseMagnificationHandler"] = \
  890.             input_event.InputEventHandler(
  891.                 mag.increaseMagnification,
  892.                 # Translators: this command increases the magnification
  893.                 # level.
  894.                 #
  895.                 _("Increases the magnification level."))
  896.  
  897.         self.inputEventHandlers["decreaseMagnificationHandler"] = \
  898.             input_event.InputEventHandler(
  899.                 mag.decreaseMagnification,
  900.                 # Translators: this command decreases the magnification
  901.                 # level.
  902.                 #
  903.                 _("Decreases the magnification level."))
  904.  
  905.         self.inputEventHandlers["toggleMagnifierHandler"] = \
  906.             input_event.InputEventHandler(
  907.                 mag.toggleMagnifier,
  908.                 # Translators: Orca allows the user to turn the magnifier
  909.                 # on or off. This command not only toggles magnification,
  910.                 # but also all of the color and pointer customizations
  911.                 # made through the magnifier.
  912.                 #
  913.                 _("Toggles the magnifier."))
  914.  
  915.         self.inputEventHandlers["cycleZoomerTypeHandler"] = \
  916.             input_event.InputEventHandler(
  917.                 mag.cycleZoomerType,
  918.                 # Translators: the user can choose between several different
  919.                 # types of magnification, including full screen and split
  920.                 # screen.  The "position" here refers to location of the
  921.                 # magnifier.
  922.                 #
  923.                 _("Cycles to the next magnifier position."))
  924.  
  925.         self.inputEventHandlers["toggleMouseReviewHandler"] = \
  926.             input_event.InputEventHandler(
  927.                 mouse_review.toggle,
  928.                 # Translators: Orca allows the item under the pointer to
  929.                 # be spoken. This toggles the feature.
  930.                 #
  931.                 _("Toggle mouse review mode."))
  932.  
  933.         self.inputEventHandlers["bypassNextCommandHandler"] = \
  934.             input_event.InputEventHandler(
  935.                 Script.bypassNextCommand,
  936.                 # Translators: Orca normally intercepts all keyboard
  937.                 # commands and only passes them along to the current
  938.                 # application when they are not Orca commands.  This
  939.                 # command causes the next command issued to be passed
  940.                 # along to the current application, bypassing Orca's
  941.                 # interception of it.
  942.                 #
  943.                 _("Passes the next command on to the current application."))
  944.  
  945.     def getInputEventHandlerKey(self, inputEventHandler):
  946.         """Returns the name of the key that contains an inputEventHadler
  947.         passed as argument
  948.         """
  949.  
  950.         for keyName, handler in self.inputEventHandlers.iteritems():
  951.             if handler == inputEventHandler:
  952.                 return keyName
  953.  
  954.         return None
  955.  
  956.     def getListeners(self):
  957.         """Sets up the AT-SPI event listeners for this script.
  958.         """
  959.         listeners = script.Script.getListeners(self)
  960.         listeners["focus:"]                                 = \
  961.             self.onFocus
  962.         #listeners["keyboard:modifiers"]                     = \
  963.         #    self.noOp
  964.         listeners["mouse:button"]                           = \
  965.             self.onMouseButton
  966.         listeners["object:property-change:accessible-name"] = \
  967.             self.onNameChanged
  968.         listeners["object:text-caret-moved"]                = \
  969.             self.onCaretMoved
  970.         listeners["object:text-changed:delete"]             = \
  971.             self.onTextDeleted
  972.         listeners["object:text-changed:insert"]             = \
  973.             self.onTextInserted
  974.         listeners["object:active-descendant-changed"]       = \
  975.             self.onActiveDescendantChanged
  976.         listeners["object:link-selected"]                   = \
  977.             self.onLinkSelected
  978.         listeners["object:state-changed:active"]            = \
  979.             self.onStateChanged
  980.         listeners["object:state-changed:focused"]           = \
  981.             self.onStateChanged
  982.         listeners["object:state-changed:showing"]           = \
  983.             self.onStateChanged
  984.         listeners["object:state-changed:checked"]           = \
  985.             self.onStateChanged
  986.         listeners["object:state-changed:pressed"]           = \
  987.             self.onStateChanged
  988.         listeners["object:state-changed:indeterminate"]     = \
  989.             self.onStateChanged
  990.         listeners["object:state-changed:expanded"]          = \
  991.             self.onStateChanged
  992.         listeners["object:state-changed:selected"]          = \
  993.             self.onStateChanged
  994.         listeners["object:text-selection-changed"]          = \
  995.             self.onTextSelectionChanged
  996.         listeners["object:selection-changed"]               = \
  997.             self.onSelectionChanged
  998.         listeners["object:property-change:accessible-value"] = \
  999.             self.onValueChanged
  1000.         listeners["object:value-changed"]                   = \
  1001.             self.onValueChanged
  1002.         listeners["window:activate"]                        = \
  1003.             self.onWindowActivated
  1004.         listeners["window:deactivate"]                      = \
  1005.             self.onWindowDeactivated
  1006.         listeners["window:create"]                          = \
  1007.             self.noOp
  1008.  
  1009.         return listeners
  1010.  
  1011.     def __getDesktopBindings(self):
  1012.         """Returns an instance of keybindings.KeyBindings that use the
  1013.         numeric keypad for focus tracking and flat review.
  1014.         """
  1015.  
  1016.         keyBindings = keybindings.KeyBindings()
  1017.  
  1018.         # We want the user to be able to combine modifiers with the
  1019.         # mouse click (e.g. to Shift+Click and select), therefore we
  1020.         # do not "care" about the modifiers.
  1021.         #
  1022.         keyBindings.add(
  1023.             keybindings.KeyBinding(
  1024.                 "KP_Divide",
  1025.                 settings.NO_MODIFIER_MASK,
  1026.                 settings.NO_MODIFIER_MASK,
  1027.                 self.inputEventHandlers["leftClickReviewItemHandler"]))
  1028.  
  1029.         keyBindings.add(
  1030.             keybindings.KeyBinding(
  1031.                 "KP_Multiply",
  1032.                 settings.NO_MODIFIER_MASK,
  1033.                 settings.NO_MODIFIER_MASK,
  1034.                 self.inputEventHandlers["rightClickReviewItemHandler"]))
  1035.  
  1036.         keyBindings.add(
  1037.             keybindings.KeyBinding(
  1038.                 "KP_Subtract",
  1039.                 settings.defaultModifierMask,
  1040.                 settings.NO_MODIFIER_MASK,
  1041.                 self.inputEventHandlers["toggleFlatReviewModeHandler"]))
  1042.  
  1043.         keyBindings.add(
  1044.             keybindings.KeyBinding(
  1045.                 "KP_Add",
  1046.                 settings.defaultModifierMask,
  1047.                 settings.NO_MODIFIER_MASK,
  1048.                 self.inputEventHandlers["sayAllHandler"]))
  1049.  
  1050.         keyBindings.add(
  1051.             keybindings.KeyBinding(
  1052.                 "KP_Enter",
  1053.                 settings.defaultModifierMask,
  1054.                 settings.NO_MODIFIER_MASK,
  1055.                 self.inputEventHandlers["whereAmIBasicHandler"],
  1056.                 1))
  1057.  
  1058.         keyBindings.add(
  1059.             keybindings.KeyBinding(
  1060.                 "KP_Enter",
  1061.                 settings.defaultModifierMask,
  1062.                 settings.NO_MODIFIER_MASK,
  1063.                 self.inputEventHandlers["whereAmIDetailedHandler"],
  1064.                 2))
  1065.  
  1066.         keyBindings.add(
  1067.             keybindings.KeyBinding(
  1068.                 "KP_Enter",
  1069.                 settings.defaultModifierMask,
  1070.                 settings.ORCA_MODIFIER_MASK,
  1071.                 self.inputEventHandlers["getTitleHandler"],
  1072.                 1))
  1073.  
  1074.         keyBindings.add(
  1075.             keybindings.KeyBinding(
  1076.                 "KP_Enter",
  1077.                 settings.defaultModifierMask,
  1078.                 settings.ORCA_MODIFIER_MASK,
  1079.                 self.inputEventHandlers["getStatusBarHandler"],
  1080.                 2))
  1081.  
  1082.         keyBindings.add(
  1083.             keybindings.KeyBinding(
  1084.                 "KP_Delete",
  1085.                 settings.defaultModifierMask,
  1086.                 settings.NO_MODIFIER_MASK,
  1087.                 self.inputEventHandlers["findHandler"]))
  1088.  
  1089.         keyBindings.add(
  1090.             keybindings.KeyBinding(
  1091.                 "KP_Delete",
  1092.                 settings.defaultModifierMask,
  1093.                 settings.ORCA_MODIFIER_MASK,
  1094.                 self.inputEventHandlers["findNextHandler"]))
  1095.  
  1096.         keyBindings.add(
  1097.             keybindings.KeyBinding(
  1098.                 "KP_Delete",
  1099.                 settings.defaultModifierMask,
  1100.                 settings.ORCA_SHIFT_MODIFIER_MASK,
  1101.                 self.inputEventHandlers["findPreviousHandler"]))
  1102.  
  1103.         keyBindings.add(
  1104.             keybindings.KeyBinding(
  1105.                 "KP_7",
  1106.                 settings.defaultModifierMask,
  1107.                 settings.NO_MODIFIER_MASK,
  1108.                 self.inputEventHandlers["reviewPreviousLineHandler"]))
  1109.  
  1110.         keyBindings.add(
  1111.             keybindings.KeyBinding(
  1112.                 "KP_Home",
  1113.                 settings.defaultModifierMask,
  1114.                 settings.NO_MODIFIER_MASK,
  1115.                 self.inputEventHandlers["reviewPreviousLineHandler"]))
  1116.  
  1117.         keyBindings.add(
  1118.             keybindings.KeyBinding(
  1119.                 "KP_7",
  1120.                 settings.defaultModifierMask,
  1121.                 settings.ORCA_MODIFIER_MASK,
  1122.                 self.inputEventHandlers["reviewHomeHandler"]))
  1123.  
  1124.         keyBindings.add(
  1125.             keybindings.KeyBinding(
  1126.                 "KP_Home",
  1127.                 settings.defaultModifierMask,
  1128.                 settings.ORCA_MODIFIER_MASK,
  1129.                 self.inputEventHandlers["reviewHomeHandler"]))
  1130.  
  1131.         keyBindings.add(
  1132.             keybindings.KeyBinding(
  1133.                 "KP_8",
  1134.                 settings.defaultModifierMask,
  1135.                 settings.NO_MODIFIER_MASK,
  1136.                 self.inputEventHandlers["reviewCurrentLineHandler"],
  1137.                 1))
  1138.  
  1139.         keyBindings.add(
  1140.             keybindings.KeyBinding(
  1141.                 "KP_8",
  1142.                 settings.defaultModifierMask,
  1143.                 settings.NO_MODIFIER_MASK,
  1144.                 self.inputEventHandlers["reviewSpellCurrentLineHandler"],
  1145.                 2))
  1146.  
  1147.         keyBindings.add(
  1148.             keybindings.KeyBinding(
  1149.                 "KP_8",
  1150.                 settings.defaultModifierMask,
  1151.                 settings.NO_MODIFIER_MASK,
  1152.                 self.inputEventHandlers["reviewPhoneticCurrentLineHandler"],
  1153.                 3))
  1154.  
  1155.         keyBindings.add(
  1156.             keybindings.KeyBinding(
  1157.                 "KP_Up",
  1158.                 settings.defaultModifierMask,
  1159.                 settings.NO_MODIFIER_MASK,
  1160.                 self.inputEventHandlers["reviewCurrentLineHandler"],
  1161.                 1))
  1162.  
  1163.         keyBindings.add(
  1164.             keybindings.KeyBinding(
  1165.                 "KP_Up",
  1166.                 settings.defaultModifierMask,
  1167.                 settings.NO_MODIFIER_MASK,
  1168.                 self.inputEventHandlers["reviewSpellCurrentLineHandler"],
  1169.                 2))
  1170.  
  1171.         keyBindings.add(
  1172.             keybindings.KeyBinding(
  1173.                 "KP_Up",
  1174.                 settings.defaultModifierMask,
  1175.                 settings.NO_MODIFIER_MASK,
  1176.                 self.inputEventHandlers["reviewPhoneticCurrentLineHandler"],
  1177.                 3))
  1178.  
  1179.         keyBindings.add(
  1180.             keybindings.KeyBinding(
  1181.                 "KP_9",
  1182.                 settings.defaultModifierMask,
  1183.                 settings.NO_MODIFIER_MASK,
  1184.                 self.inputEventHandlers["reviewNextLineHandler"]))
  1185.  
  1186.         keyBindings.add(
  1187.             keybindings.KeyBinding(
  1188.                 "KP_Page_Up",
  1189.                 settings.defaultModifierMask,
  1190.                 settings.NO_MODIFIER_MASK,
  1191.                 self.inputEventHandlers["reviewNextLineHandler"]))
  1192.  
  1193.         keyBindings.add(
  1194.             keybindings.KeyBinding(
  1195.                 "KP_9",
  1196.                 settings.defaultModifierMask,
  1197.                 settings.ORCA_MODIFIER_MASK,
  1198.                 self.inputEventHandlers["reviewEndHandler"]))
  1199.  
  1200.         keyBindings.add(
  1201.             keybindings.KeyBinding(
  1202.                 "KP_Page_Up",
  1203.                 settings.defaultModifierMask,
  1204.                 settings.ORCA_MODIFIER_MASK,
  1205.                 self.inputEventHandlers["reviewEndHandler"]))
  1206.  
  1207.         keyBindings.add(
  1208.             keybindings.KeyBinding(
  1209.                 "KP_4",
  1210.                 settings.defaultModifierMask,
  1211.                 settings.NO_MODIFIER_MASK,
  1212.                 self.inputEventHandlers["reviewPreviousItemHandler"]))
  1213.  
  1214.         keyBindings.add(
  1215.             keybindings.KeyBinding(
  1216.                 "KP_Left",
  1217.                 settings.defaultModifierMask,
  1218.                 settings.NO_MODIFIER_MASK,
  1219.                 self.inputEventHandlers["reviewPreviousItemHandler"]))
  1220.  
  1221.         keyBindings.add(
  1222.             keybindings.KeyBinding(
  1223.                 "KP_4",
  1224.                 settings.defaultModifierMask,
  1225.                 settings.ORCA_MODIFIER_MASK,
  1226.                 self.inputEventHandlers["reviewAboveHandler"]))
  1227.  
  1228.         keyBindings.add(
  1229.             keybindings.KeyBinding(
  1230.                 "KP_Left",
  1231.                 settings.defaultModifierMask,
  1232.                 settings.ORCA_MODIFIER_MASK,
  1233.                 self.inputEventHandlers["reviewAboveHandler"]))
  1234.  
  1235.         keyBindings.add(
  1236.             keybindings.KeyBinding(
  1237.                 "KP_5",
  1238.                 settings.defaultModifierMask,
  1239.                 settings.NO_MODIFIER_MASK,
  1240.                 self.inputEventHandlers["reviewCurrentItemHandler"],
  1241.                 1))
  1242.  
  1243.         keyBindings.add(
  1244.             keybindings.KeyBinding(
  1245.                 "KP_5",
  1246.                 settings.defaultModifierMask,
  1247.                 settings.NO_MODIFIER_MASK,
  1248.                 self.inputEventHandlers["reviewSpellCurrentItemHandler"],
  1249.                 2))
  1250.  
  1251.         keyBindings.add(
  1252.             keybindings.KeyBinding(
  1253.                 "KP_5",
  1254.                 settings.defaultModifierMask,
  1255.                 settings.NO_MODIFIER_MASK,
  1256.                 self.inputEventHandlers["reviewPhoneticCurrentItemHandler"],
  1257.                 3))
  1258.  
  1259.         keyBindings.add(
  1260.             keybindings.KeyBinding(
  1261.                 "KP_Begin",
  1262.                 settings.defaultModifierMask,
  1263.                 settings.NO_MODIFIER_MASK,
  1264.                 self.inputEventHandlers["reviewCurrentItemHandler"],
  1265.                 1))
  1266.  
  1267.         keyBindings.add(
  1268.             keybindings.KeyBinding(
  1269.                 "KP_Begin",
  1270.                 settings.defaultModifierMask,
  1271.                 settings.NO_MODIFIER_MASK,
  1272.                 self.inputEventHandlers["reviewSpellCurrentItemHandler"],
  1273.                 2))
  1274.  
  1275.         keyBindings.add(
  1276.             keybindings.KeyBinding(
  1277.                 "KP_Begin",
  1278.                 settings.defaultModifierMask,
  1279.                 settings.NO_MODIFIER_MASK,
  1280.                 self.inputEventHandlers["reviewPhoneticCurrentItemHandler"],
  1281.                 3))
  1282.  
  1283.         keyBindings.add(
  1284.             keybindings.KeyBinding(
  1285.                 "KP_5",
  1286.                 settings.defaultModifierMask,
  1287.                 settings.ORCA_MODIFIER_MASK,
  1288.                 self.inputEventHandlers["reviewCurrentAccessibleHandler"]))
  1289.  
  1290.         keyBindings.add(
  1291.             keybindings.KeyBinding(
  1292.                 "KP_Begin",
  1293.                 settings.defaultModifierMask,
  1294.                 settings.ORCA_MODIFIER_MASK,
  1295.                 self.inputEventHandlers["reviewCurrentAccessibleHandler"]))
  1296.  
  1297.         keyBindings.add(
  1298.             keybindings.KeyBinding(
  1299.                 "KP_6",
  1300.                 settings.defaultModifierMask,
  1301.                 settings.NO_MODIFIER_MASK,
  1302.                 self.inputEventHandlers["reviewNextItemHandler"]))
  1303.  
  1304.         keyBindings.add(
  1305.             keybindings.KeyBinding(
  1306.                 "KP_Right",
  1307.                 settings.defaultModifierMask,
  1308.                 settings.NO_MODIFIER_MASK,
  1309.                 self.inputEventHandlers["reviewNextItemHandler"]))
  1310.  
  1311.         keyBindings.add(
  1312.             keybindings.KeyBinding(
  1313.                 "KP_6",
  1314.                 settings.defaultModifierMask,
  1315.                 settings.ORCA_MODIFIER_MASK,
  1316.                 self.inputEventHandlers["reviewBelowHandler"]))
  1317.  
  1318.         keyBindings.add(
  1319.             keybindings.KeyBinding(
  1320.                 "KP_Right",
  1321.                 settings.defaultModifierMask,
  1322.                 settings.ORCA_MODIFIER_MASK,
  1323.                 self.inputEventHandlers["reviewBelowHandler"]))
  1324.  
  1325.         keyBindings.add(
  1326.             keybindings.KeyBinding(
  1327.                 "KP_1",
  1328.                 settings.defaultModifierMask,
  1329.                 settings.NO_MODIFIER_MASK,
  1330.                 self.inputEventHandlers["reviewPreviousCharacterHandler"]))
  1331.  
  1332.         keyBindings.add(
  1333.             keybindings.KeyBinding(
  1334.                 "KP_End",
  1335.                 settings.defaultModifierMask,
  1336.                 settings.NO_MODIFIER_MASK,
  1337.                 self.inputEventHandlers["reviewPreviousCharacterHandler"]))
  1338.  
  1339.         keyBindings.add(
  1340.             keybindings.KeyBinding(
  1341.                 "KP_1",
  1342.                 settings.defaultModifierMask,
  1343.                 settings.ORCA_MODIFIER_MASK,
  1344.                 self.inputEventHandlers["reviewEndOfLineHandler"]))
  1345.  
  1346.         keyBindings.add(
  1347.             keybindings.KeyBinding(
  1348.                 "KP_End",
  1349.                 settings.defaultModifierMask,
  1350.                 settings.ORCA_MODIFIER_MASK,
  1351.                 self.inputEventHandlers["reviewEndOfLineHandler"]))
  1352.  
  1353.         keyBindings.add(
  1354.             keybindings.KeyBinding(
  1355.                 "KP_2",
  1356.                 settings.defaultModifierMask,
  1357.                 settings.NO_MODIFIER_MASK,
  1358.                 self.inputEventHandlers["reviewCurrentCharacterHandler"],
  1359.                 1))
  1360.  
  1361.         keyBindings.add(
  1362.             keybindings.KeyBinding(
  1363.                 "KP_2",
  1364.                 settings.defaultModifierMask,
  1365.                 settings.NO_MODIFIER_MASK,
  1366.                 self.inputEventHandlers["reviewSpellCurrentCharacterHandler"],
  1367.                 2))
  1368.  
  1369.         keyBindings.add(
  1370.             keybindings.KeyBinding(
  1371.                 "KP_Down",
  1372.                 settings.defaultModifierMask,
  1373.                 settings.NO_MODIFIER_MASK,
  1374.                 self.inputEventHandlers["reviewCurrentCharacterHandler"],
  1375.                 1))
  1376.  
  1377.         keyBindings.add(
  1378.             keybindings.KeyBinding(
  1379.                 "KP_Down",
  1380.                 settings.defaultModifierMask,
  1381.                 settings.NO_MODIFIER_MASK,
  1382.                 self.inputEventHandlers["reviewSpellCurrentCharacterHandler"],
  1383.                 2))
  1384.  
  1385.         keyBindings.add(
  1386.             keybindings.KeyBinding(
  1387.                 "KP_3",
  1388.                 settings.defaultModifierMask,
  1389.                 settings.NO_MODIFIER_MASK,
  1390.                 self.inputEventHandlers["reviewNextCharacterHandler"]))
  1391.  
  1392.         keyBindings.add(
  1393.             keybindings.KeyBinding(
  1394.                 "KP_Page_Down",
  1395.                 settings.defaultModifierMask,
  1396.                 settings.NO_MODIFIER_MASK,
  1397.                 self.inputEventHandlers["reviewNextCharacterHandler"]))
  1398.  
  1399.         return keyBindings
  1400.  
  1401.     def __getLaptopBindings(self):
  1402.         """Returns an instance of keybindings.KeyBindings that use the
  1403.         the main keyboard keys for focus tracking and flat review.
  1404.         """
  1405.  
  1406.         keyBindings = keybindings.KeyBindings()
  1407.  
  1408.         # We want the user to be able to combine modifiers with the
  1409.         # mouse click (e.g. to Shift+Click and select), therefore we
  1410.         # do not "care" about the modifiers (other than the Orca
  1411.         # modifier).
  1412.         #
  1413.         keyBindings.add(
  1414.             keybindings.KeyBinding(
  1415.                 "7",
  1416.                 settings.ORCA_MODIFIER_MASK,
  1417.                 settings.ORCA_MODIFIER_MASK,
  1418.                 self.inputEventHandlers["leftClickReviewItemHandler"]))
  1419.  
  1420.         keyBindings.add(
  1421.             keybindings.KeyBinding(
  1422.                 "8",
  1423.                 settings.ORCA_MODIFIER_MASK,
  1424.                 settings.ORCA_MODIFIER_MASK,
  1425.                 self.inputEventHandlers["rightClickReviewItemHandler"]))
  1426.  
  1427.         keyBindings.add(
  1428.             keybindings.KeyBinding(
  1429.                 "p",
  1430.                 settings.defaultModifierMask,
  1431.                 settings.ORCA_MODIFIER_MASK,
  1432.                 self.inputEventHandlers["toggleFlatReviewModeHandler"]))
  1433.  
  1434.         keyBindings.add(
  1435.             keybindings.KeyBinding(
  1436.                 "semicolon",
  1437.                 settings.defaultModifierMask,
  1438.                 settings.ORCA_MODIFIER_MASK,
  1439.                 self.inputEventHandlers["sayAllHandler"]))
  1440.  
  1441.         keyBindings.add(
  1442.             keybindings.KeyBinding(
  1443.                 "Return",
  1444.                 settings.defaultModifierMask,
  1445.                 settings.ORCA_MODIFIER_MASK,
  1446.                 self.inputEventHandlers["whereAmIBasicHandler"],
  1447.                 1))
  1448.  
  1449.         keyBindings.add(
  1450.             keybindings.KeyBinding(
  1451.                 "Return",
  1452.                 settings.defaultModifierMask,
  1453.                 settings.ORCA_MODIFIER_MASK,
  1454.                 self.inputEventHandlers["whereAmIDetailedHandler"],
  1455.                 2))
  1456.  
  1457.         keyBindings.add(
  1458.             keybindings.KeyBinding(
  1459.                 "slash",
  1460.                 settings.defaultModifierMask,
  1461.                 settings.ORCA_MODIFIER_MASK,
  1462.                 self.inputEventHandlers["getTitleHandler"],
  1463.                 1))
  1464.  
  1465.         keyBindings.add(
  1466.             keybindings.KeyBinding(
  1467.                 "slash",
  1468.                 settings.defaultModifierMask,
  1469.                 settings.ORCA_MODIFIER_MASK,
  1470.                 self.inputEventHandlers["getStatusBarHandler"],
  1471.                 2))
  1472.  
  1473.         keyBindings.add(
  1474.             keybindings.KeyBinding(
  1475.                 "bracketleft",
  1476.                 settings.defaultModifierMask,
  1477.                 settings.ORCA_MODIFIER_MASK,
  1478.                 self.inputEventHandlers["findHandler"]))
  1479.  
  1480.         keyBindings.add(
  1481.             keybindings.KeyBinding(
  1482.                 "bracketright",
  1483.                 settings.defaultModifierMask,
  1484.                 settings.ORCA_MODIFIER_MASK,
  1485.                 self.inputEventHandlers["findNextHandler"]))
  1486.  
  1487.         keyBindings.add(
  1488.             keybindings.KeyBinding(
  1489.                 "bracketright",
  1490.                 settings.defaultModifierMask,
  1491.                 settings.ORCA_CTRL_MODIFIER_MASK,
  1492.                 self.inputEventHandlers["findPreviousHandler"]))
  1493.  
  1494.         keyBindings.add(
  1495.             keybindings.KeyBinding(
  1496.                 "u",
  1497.                 settings.defaultModifierMask,
  1498.                 settings.ORCA_MODIFIER_MASK,
  1499.                 self.inputEventHandlers["reviewPreviousLineHandler"]))
  1500.  
  1501.         keyBindings.add(
  1502.             keybindings.KeyBinding(
  1503.                 "u",
  1504.                 settings.defaultModifierMask,
  1505.                 settings.ORCA_CTRL_MODIFIER_MASK,
  1506.                 self.inputEventHandlers["reviewHomeHandler"]))
  1507.  
  1508.         keyBindings.add(
  1509.             keybindings.KeyBinding(
  1510.                 "i",
  1511.                 settings.defaultModifierMask,
  1512.                 settings.ORCA_MODIFIER_MASK,
  1513.                 self.inputEventHandlers["reviewCurrentLineHandler"],
  1514.                 1))
  1515.  
  1516.         keyBindings.add(
  1517.             keybindings.KeyBinding(
  1518.                 "i",
  1519.                 settings.defaultModifierMask,
  1520.                 settings.ORCA_MODIFIER_MASK,
  1521.                 self.inputEventHandlers["reviewSpellCurrentLineHandler"],
  1522.                 2))
  1523.  
  1524.         keyBindings.add(
  1525.             keybindings.KeyBinding(
  1526.                 "i",
  1527.                 settings.defaultModifierMask,
  1528.                 settings.ORCA_MODIFIER_MASK,
  1529.                 self.inputEventHandlers["reviewPhoneticCurrentLineHandler"],
  1530.                 3))
  1531.  
  1532.         keyBindings.add(
  1533.             keybindings.KeyBinding(
  1534.                 "o",
  1535.                 settings.defaultModifierMask,
  1536.                 settings.ORCA_MODIFIER_MASK,
  1537.                 self.inputEventHandlers["reviewNextLineHandler"]))
  1538.  
  1539.         keyBindings.add(
  1540.             keybindings.KeyBinding(
  1541.                 "o",
  1542.                 settings.defaultModifierMask,
  1543.                 settings.ORCA_CTRL_MODIFIER_MASK,
  1544.                 self.inputEventHandlers["reviewEndHandler"]))
  1545.  
  1546.         keyBindings.add(
  1547.             keybindings.KeyBinding(
  1548.                 "j",
  1549.                 settings.defaultModifierMask,
  1550.                 settings.ORCA_MODIFIER_MASK,
  1551.                 self.inputEventHandlers["reviewPreviousItemHandler"]))
  1552.  
  1553.         keyBindings.add(
  1554.             keybindings.KeyBinding(
  1555.                 "j",
  1556.                 settings.defaultModifierMask,
  1557.                 settings.ORCA_CTRL_MODIFIER_MASK,
  1558.                 self.inputEventHandlers["reviewAboveHandler"]))
  1559.  
  1560.         keyBindings.add(
  1561.             keybindings.KeyBinding(
  1562.                 "k",
  1563.                 settings.defaultModifierMask,
  1564.                 settings.ORCA_MODIFIER_MASK,
  1565.                 self.inputEventHandlers["reviewCurrentItemHandler"],
  1566.                 1))
  1567.  
  1568.         keyBindings.add(
  1569.             keybindings.KeyBinding(
  1570.                 "k",
  1571.                 settings.defaultModifierMask,
  1572.                 settings.ORCA_MODIFIER_MASK,
  1573.                 self.inputEventHandlers["reviewSpellCurrentItemHandler"],
  1574.                 2))
  1575.  
  1576.         keyBindings.add(
  1577.             keybindings.KeyBinding(
  1578.                 "k",
  1579.                 settings.defaultModifierMask,
  1580.                 settings.ORCA_MODIFIER_MASK,
  1581.                 self.inputEventHandlers["reviewPhoneticCurrentItemHandler"],
  1582.                 3))
  1583.  
  1584.         keyBindings.add(
  1585.             keybindings.KeyBinding(
  1586.                 "k",
  1587.                 settings.defaultModifierMask,
  1588.                 settings.ORCA_CTRL_MODIFIER_MASK,
  1589.                 self.inputEventHandlers["reviewCurrentAccessibleHandler"]))
  1590.  
  1591.         keyBindings.add(
  1592.             keybindings.KeyBinding(
  1593.                 "l",
  1594.                 settings.defaultModifierMask,
  1595.                 settings.ORCA_MODIFIER_MASK,
  1596.                 self.inputEventHandlers["reviewNextItemHandler"]))
  1597.  
  1598.         keyBindings.add(
  1599.             keybindings.KeyBinding(
  1600.                 "l",
  1601.                 settings.defaultModifierMask,
  1602.                 settings.ORCA_CTRL_MODIFIER_MASK,
  1603.                 self.inputEventHandlers["reviewBelowHandler"]))
  1604.  
  1605.         keyBindings.add(
  1606.             keybindings.KeyBinding(
  1607.                 "m",
  1608.                 settings.defaultModifierMask,
  1609.                 settings.ORCA_MODIFIER_MASK,
  1610.                 self.inputEventHandlers["reviewPreviousCharacterHandler"]))
  1611.  
  1612.         keyBindings.add(
  1613.             keybindings.KeyBinding(
  1614.                 "m",
  1615.                 settings.defaultModifierMask,
  1616.                 settings.ORCA_CTRL_MODIFIER_MASK,
  1617.                 self.inputEventHandlers["reviewEndOfLineHandler"]))
  1618.  
  1619.         keyBindings.add(
  1620.             keybindings.KeyBinding(
  1621.                 "comma",
  1622.                 settings.defaultModifierMask,
  1623.                 settings.ORCA_MODIFIER_MASK,
  1624.                 self.inputEventHandlers["reviewCurrentCharacterHandler"],
  1625.                 1))
  1626.  
  1627.         keyBindings.add(
  1628.             keybindings.KeyBinding(
  1629.                 "comma",
  1630.                 settings.defaultModifierMask,
  1631.                 settings.ORCA_MODIFIER_MASK,
  1632.                 self.inputEventHandlers["reviewSpellCurrentCharacterHandler"],
  1633.                 2))
  1634.  
  1635.         keyBindings.add(
  1636.             keybindings.KeyBinding(
  1637.                 "period",
  1638.                 settings.defaultModifierMask,
  1639.                 settings.ORCA_MODIFIER_MASK,
  1640.                 self.inputEventHandlers["reviewNextCharacterHandler"]))
  1641.  
  1642.         return keyBindings
  1643.  
  1644.     def getKeyBindings(self):
  1645.         """Defines the key bindings for this script.
  1646.  
  1647.         Returns an instance of keybindings.KeyBindings.
  1648.         """
  1649.  
  1650.         keyBindings = script.Script.getKeyBindings(self)
  1651.  
  1652.         if settings.keyboardLayout == settings.GENERAL_KEYBOARD_LAYOUT_DESKTOP:
  1653.             for keyBinding in self.__getDesktopBindings().keyBindings:
  1654.                 keyBindings.add(keyBinding)
  1655.         else:
  1656.             for keyBinding in self.__getLaptopBindings().keyBindings:
  1657.                 keyBindings.add(keyBinding)
  1658.  
  1659.         keyBindings.add(
  1660.             keybindings.KeyBinding(
  1661.                 "Num_Lock",
  1662.                 settings.defaultModifierMask,
  1663.                 settings.ORCA_MODIFIER_MASK,
  1664.                 self.inputEventHandlers["showZonesHandler"]))
  1665.  
  1666.         keyBindings.add(
  1667.             keybindings.KeyBinding(
  1668.                 "F11",
  1669.                 settings.defaultModifierMask,
  1670.                 settings.ORCA_MODIFIER_MASK,
  1671.                 self.inputEventHandlers["toggleTableCellReadModeHandler"]))
  1672.  
  1673.         keyBindings.add(
  1674.             keybindings.KeyBinding(
  1675.                 "SunF36",
  1676.                 settings.defaultModifierMask,
  1677.                 settings.ORCA_MODIFIER_MASK,
  1678.                 self.inputEventHandlers["toggleTableCellReadModeHandler"]))
  1679.  
  1680.         keyBindings.add(
  1681.             keybindings.KeyBinding(
  1682.                 "f",
  1683.                 settings.defaultModifierMask,
  1684.                 settings.ORCA_MODIFIER_MASK,
  1685.                 self.inputEventHandlers["readCharAttributesHandler"]))
  1686.  
  1687.         keyBindings.add(
  1688.             keybindings.KeyBinding(
  1689.                 "h",
  1690.                 settings.defaultModifierMask,
  1691.                 settings.ORCA_MODIFIER_MASK,
  1692.                 self.inputEventHandlers["enterLearnModeHandler"]))
  1693.  
  1694.         keyBindings.add(
  1695.             keybindings.KeyBinding(
  1696.                 "q",
  1697.                 settings.defaultModifierMask,
  1698.                 settings.ORCA_MODIFIER_MASK,
  1699.                 self.inputEventHandlers["shutdownHandler"]))
  1700.  
  1701.         keyBindings.add(
  1702.             keybindings.KeyBinding(
  1703.                 "space",
  1704.                 settings.defaultModifierMask,
  1705.                 settings.ORCA_MODIFIER_MASK,
  1706.                 self.inputEventHandlers["preferencesSettingsHandler"]))
  1707.  
  1708.         keyBindings.add(
  1709.             keybindings.KeyBinding(
  1710.                 "space",
  1711.                 settings.defaultModifierMask,
  1712.                 settings.ORCA_CTRL_MODIFIER_MASK,
  1713.                 self.inputEventHandlers["appPreferencesSettingsHandler"]))
  1714.  
  1715.         keyBindings.add(
  1716.             keybindings.KeyBinding(
  1717.                 "s",
  1718.                 settings.defaultModifierMask,
  1719.                 settings.ORCA_MODIFIER_MASK,
  1720.                 self.inputEventHandlers["toggleSilenceSpeechHandler"]))
  1721.  
  1722.         keyBindings.add(
  1723.             keybindings.KeyBinding(
  1724.                 "End",
  1725.                 settings.defaultModifierMask,
  1726.                 settings.ORCA_CTRL_ALT_MODIFIER_MASK,
  1727.                 self.inputEventHandlers["listAppsHandler"]))
  1728.  
  1729.         keyBindings.add(
  1730.             keybindings.KeyBinding(
  1731.                 "Home",
  1732.                 settings.defaultModifierMask,
  1733.                 settings.ORCA_CTRL_ALT_MODIFIER_MASK,
  1734.                 self.inputEventHandlers["reportScriptInfoHandler"]))
  1735.  
  1736.         keyBindings.add(
  1737.             keybindings.KeyBinding(
  1738.                 "Page_Up",
  1739.                 settings.defaultModifierMask,
  1740.                 settings.ORCA_CTRL_ALT_MODIFIER_MASK,
  1741.                 self.inputEventHandlers["printAncestryHandler"]))
  1742.  
  1743.         keyBindings.add(
  1744.             keybindings.KeyBinding(
  1745.                 "Page_Down",
  1746.                 settings.defaultModifierMask,
  1747.                 settings.ORCA_CTRL_ALT_MODIFIER_MASK,
  1748.                 self.inputEventHandlers["printHierarchyHandler"]))
  1749.  
  1750.         #####################################################################
  1751.         #                                                                   #
  1752.         #  Bookmark key bindings                                            #
  1753.         #                                                                   #
  1754.         #####################################################################
  1755.         # key binding to save bookmark information to disk
  1756.         keyBindings.add(
  1757.             keybindings.KeyBinding(
  1758.                 "b",
  1759.                 settings.defaultModifierMask,
  1760.                 settings.ORCA_ALT_MODIFIER_MASK,
  1761.                 self.inputEventHandlers["saveBookmarks"]))
  1762.         # key binding to move to the previous bookmark
  1763.         keyBindings.add(
  1764.             keybindings.KeyBinding(
  1765.                 "b",
  1766.                 settings.defaultModifierMask,
  1767.                 settings.ORCA_SHIFT_MODIFIER_MASK,
  1768.                 self.inputEventHandlers["goToPrevBookmark"]))
  1769.         # key binding to move to the next bookmark
  1770.         keyBindings.add(
  1771.             keybindings.KeyBinding(
  1772.                 "b",
  1773.                 settings.defaultModifierMask,
  1774.                 settings.ORCA_MODIFIER_MASK,
  1775.                 self.inputEventHandlers["goToNextBookmark"]))
  1776.  
  1777.         # key bindings for '1' through '6' for relevant commands
  1778.         for key in xrange(1, 7):
  1779.             # 'Add bookmark' key bindings
  1780.             keyBindings.add(
  1781.                 keybindings.KeyBinding(
  1782.                     str(key),
  1783.                     settings.defaultModifierMask,
  1784.                     settings.ORCA_ALT_MODIFIER_MASK,
  1785.                     self.inputEventHandlers["addBookmark"]))
  1786.  
  1787.             # 'Go to bookmark' key bindings
  1788.             keyBindings.add(
  1789.                 keybindings.KeyBinding(
  1790.                     str(key),
  1791.                     settings.defaultModifierMask,
  1792.                     settings.ORCA_MODIFIER_MASK,
  1793.                     self.inputEventHandlers["goToBookmark"]))
  1794.  
  1795.             # key binding for WhereAmI information with respect to root acc
  1796.             keyBindings.add(
  1797.                 keybindings.KeyBinding(
  1798.                     str(key),
  1799.                     settings.defaultModifierMask,
  1800.                     settings.SHIFT_ALT_MODIFIER_MASK,
  1801.                     self.inputEventHandlers["bookmarkCurrentWhereAmI"]))
  1802.  
  1803.         keyBindings.add(
  1804.             keybindings.KeyBinding(
  1805.                 "BackSpace",
  1806.                 settings.defaultModifierMask,
  1807.                 settings.ORCA_MODIFIER_MASK,
  1808.                 self.inputEventHandlers["bypassNextCommandHandler"]))
  1809.  
  1810.         #####################################################################
  1811.         #                                                                   #
  1812.         #  Unbound handlers                                                 #
  1813.         #                                                                   #
  1814.         #####################################################################
  1815.  
  1816.         keyBindings.add(
  1817.             keybindings.KeyBinding(
  1818.                 "",
  1819.                 settings.defaultModifierMask,
  1820.                 settings.NO_MODIFIER_MASK,
  1821.                 self.inputEventHandlers["reportScriptInfoHandler"]))
  1822.  
  1823.         keyBindings.add(
  1824.             keybindings.KeyBinding(
  1825.                 "",
  1826.                 settings.defaultModifierMask,
  1827.                 settings.NO_MODIFIER_MASK,
  1828.                 self.inputEventHandlers["cycleDebugLevelHandler"]))
  1829.  
  1830.         if settings.debugMemoryUsage:
  1831.             keyBindings.add(
  1832.                 keybindings.KeyBinding(
  1833.                     "",
  1834.                     settings.defaultModifierMask,
  1835.                     settings.NO_MODIFIER_MASK,
  1836.                     self.inputEventHandlers["printMemoryUsageHandler"]))
  1837.  
  1838.         keyBindings.add(
  1839.             keybindings.KeyBinding(
  1840.                 "",
  1841.                 settings.defaultModifierMask,
  1842.                 settings.NO_MODIFIER_MASK,
  1843.                 self.inputEventHandlers["decreaseSpeechRateHandler"]))
  1844.  
  1845.         keyBindings.add(
  1846.             keybindings.KeyBinding(
  1847.                 "",
  1848.                 settings.defaultModifierMask,
  1849.                 settings.NO_MODIFIER_MASK,
  1850.                 self.inputEventHandlers["increaseSpeechRateHandler"]))
  1851.  
  1852.         keyBindings.add(
  1853.             keybindings.KeyBinding(
  1854.                 "",
  1855.                 settings.defaultModifierMask,
  1856.                 settings.NO_MODIFIER_MASK,
  1857.                 self.inputEventHandlers["decreaseSpeechPitchHandler"]))
  1858.  
  1859.         keyBindings.add(
  1860.             keybindings.KeyBinding(
  1861.                 "",
  1862.                 settings.defaultModifierMask,
  1863.                 settings.NO_MODIFIER_MASK,
  1864.                 self.inputEventHandlers["increaseSpeechPitchHandler"]))
  1865.  
  1866.         keyBindings.add(
  1867.             keybindings.KeyBinding(
  1868.                 "",
  1869.                 settings.defaultModifierMask,
  1870.                 settings.NO_MODIFIER_MASK,
  1871.                 self.inputEventHandlers["toggleColorEnhancementsHandler"]))
  1872.  
  1873.         keyBindings.add(
  1874.             keybindings.KeyBinding(
  1875.                 "",
  1876.                 settings.defaultModifierMask,
  1877.                 settings.NO_MODIFIER_MASK,
  1878.                 self.inputEventHandlers["toggleMouseEnhancementsHandler"]))
  1879.  
  1880.         keyBindings.add(
  1881.             keybindings.KeyBinding(
  1882.                 "",
  1883.                 settings.defaultModifierMask,
  1884.                 settings.NO_MODIFIER_MASK,
  1885.                 self.inputEventHandlers["increaseMagnificationHandler"]))
  1886.  
  1887.         keyBindings.add(
  1888.             keybindings.KeyBinding(
  1889.                 "",
  1890.                 settings.defaultModifierMask,
  1891.                 settings.NO_MODIFIER_MASK,
  1892.                 self.inputEventHandlers["decreaseMagnificationHandler"]))
  1893.  
  1894.         keyBindings.add(
  1895.             keybindings.KeyBinding(
  1896.                 "",
  1897.                 settings.defaultModifierMask,
  1898.                 settings.NO_MODIFIER_MASK,
  1899.                 self.inputEventHandlers["toggleMagnifierHandler"]))
  1900.  
  1901.         keyBindings.add(
  1902.             keybindings.KeyBinding(
  1903.                 "",
  1904.                 settings.defaultModifierMask,
  1905.                 settings.NO_MODIFIER_MASK,
  1906.                 self.inputEventHandlers["cycleZoomerTypeHandler"]))
  1907.  
  1908.         keyBindings.add(
  1909.             keybindings.KeyBinding(
  1910.                 "",
  1911.                 settings.defaultModifierMask,
  1912.                 settings.NO_MODIFIER_MASK,
  1913.                 self.inputEventHandlers["panBrailleLeftHandler"]))
  1914.  
  1915.         keyBindings.add(
  1916.             keybindings.KeyBinding(
  1917.                 "",
  1918.                 settings.defaultModifierMask,
  1919.                 settings.NO_MODIFIER_MASK,
  1920.                 self.inputEventHandlers["panBrailleRightHandler"]))
  1921.  
  1922.         keyBindings.add(
  1923.             keybindings.KeyBinding(
  1924.                 "",
  1925.                 settings.defaultModifierMask,
  1926.                 settings.NO_MODIFIER_MASK,
  1927.                 self.inputEventHandlers["toggleMouseReviewHandler"]))
  1928.  
  1929.         keyBindings = settings.overrideKeyBindings(self, keyBindings)
  1930.  
  1931.         return keyBindings
  1932.  
  1933.     def getBrailleBindings(self):
  1934.         """Defines the braille bindings for this script.
  1935.  
  1936.         Returns a dictionary where the keys are BrlTTY commands and the
  1937.         values are InputEventHandler instances.
  1938.         """
  1939.         brailleBindings = script.Script.getBrailleBindings(self)
  1940.         brailleBindings[braille.CMD_FWINLT]   = \
  1941.             self.inputEventHandlers["panBrailleLeftHandler"]
  1942.         brailleBindings[braille.CMD_FWINRT]   = \
  1943.             self.inputEventHandlers["panBrailleRightHandler"]
  1944.         brailleBindings[braille.CMD_LNUP]     = \
  1945.             self.inputEventHandlers["reviewAboveHandler"]
  1946.         brailleBindings[braille.CMD_LNDN]     = \
  1947.             self.inputEventHandlers["reviewBelowHandler"]
  1948.         brailleBindings[braille.CMD_FREEZE]   = \
  1949.             self.inputEventHandlers["toggleFlatReviewModeHandler"]
  1950.         brailleBindings[braille.CMD_TOP_LEFT] = \
  1951.             self.inputEventHandlers["reviewHomeHandler"]
  1952.         brailleBindings[braille.CMD_BOT_LEFT] = \
  1953.             self.inputEventHandlers["reviewBottomLeftHandler"]
  1954.         brailleBindings[braille.CMD_HOME]     = \
  1955.             self.inputEventHandlers["goBrailleHomeHandler"]
  1956.  
  1957.         return brailleBindings
  1958.  
  1959.     def processKeyboardEvent(self, keyboardEvent):
  1960.         """Processes the given keyboard event. It uses the super
  1961.         class equivalent to do most of the work. The only thing done here
  1962.         is to detect when the user is trying to get out of learn mode.
  1963.  
  1964.         Arguments:
  1965.         - keyboardEvent: an instance of input_event.KeyboardEvent
  1966.         """
  1967.  
  1968.         return script.Script.processKeyboardEvent(self, keyboardEvent)
  1969.  
  1970.     def __sayAllProgressCallback(self, context, progressType):
  1971.         # [[[TODO: WDW - this needs work.  Need to be able to manage
  1972.         # the monitoring of progress and couple that with both updating
  1973.         # the visual progress of what is being spoken as well as
  1974.         # positioning the cursor when speech has stopped.]]]
  1975.         #
  1976.         text = context.obj.queryText()
  1977.         if progressType == speechserver.SayAllContext.PROGRESS:
  1978.             #print "PROGRESS", context.utterance, context.currentOffset
  1979.             #obj = context.obj
  1980.             #[x, y, width, height] = obj.text.getCharacterExtents(
  1981.             #    context.currentOffset, 0)
  1982.             #print context.currentOffset, x, y, width, height
  1983.             #self.drawOutline(x, y, width, height)
  1984.             return
  1985.         elif progressType == speechserver.SayAllContext.INTERRUPTED:
  1986.             #print "INTERRUPTED", context.utterance, context.currentOffset
  1987.             text.setCaretOffset(context.currentOffset)
  1988.         elif progressType == speechserver.SayAllContext.COMPLETED:
  1989.             #print "COMPLETED", context.utterance, context.currentOffset
  1990.             orca.setLocusOfFocus(None, context.obj, False)
  1991.             text.setCaretOffset(context.currentOffset)
  1992.  
  1993.         # If there is a selection, clear it. See bug #489504 for more details.
  1994.         #
  1995.         if text.getNSelections():
  1996.             text.setSelection(0, context.currentOffset, context.currentOffset)
  1997.  
  1998.     def sayAll(self, inputEvent):
  1999.         clickCount = self.getClickCount()
  2000.         doubleClick = clickCount == 2
  2001.         self.lastSayAllEvent = inputEvent
  2002.  
  2003.         if doubleClick:
  2004.             # Try to "say all" for the current dialog/window by flat
  2005.             # reviewing everything. See bug #354462 for more details.
  2006.             #
  2007.             context = self.getFlatReviewContext()
  2008.  
  2009.             utterances = []
  2010.             context.goBegin()
  2011.             while True:
  2012.                 [wordString, x, y, width, height] = \
  2013.                          context.getCurrent(flat_review.Context.ZONE)
  2014.  
  2015.                 utterances.append(wordString)
  2016.  
  2017.                 moved = context.goNext(flat_review.Context.ZONE,
  2018.                                        flat_review.Context.WRAP_LINE)
  2019.  
  2020.                 if not moved:
  2021.                     break
  2022.  
  2023.             speech.speakUtterances(utterances)
  2024.  
  2025.         elif self.isTextArea(orca_state.locusOfFocus):
  2026.             try:
  2027.                 orca_state.locusOfFocus.queryText()
  2028.             except NotImplementedError:
  2029.                 utterances = self.speechGenerator.getSpeech(
  2030.                              orca_state.locusOfFocus, False)
  2031.                 utterances.extend(self.tutorialGenerator.getTutorial(
  2032.                            orca_state.locusOfFocus, False))
  2033.                 speech.speakUtterances(utterances)
  2034.             except AttributeError:
  2035.                 pass
  2036.             else:
  2037.                 speech.sayAll(self.textLines(orca_state.locusOfFocus),
  2038.                               self.__sayAllProgressCallback)
  2039.  
  2040.         return True
  2041.  
  2042.     def isTextArea(self, obj):
  2043.         """Returns True if obj is a GUI component that is for entering text.
  2044.  
  2045.         Arguments:
  2046.         - obj: an accessible
  2047.         """
  2048.         return obj and \
  2049.             obj.getRole() in (pyatspi.ROLE_TEXT,
  2050.                               pyatspi.ROLE_PARAGRAPH,
  2051.                               pyatspi.ROLE_TERMINAL)
  2052.  
  2053.     def isReadOnlyTextArea(self, obj):
  2054.         """Returns True if obj is a text entry area that is read only."""
  2055.         state = obj.getState()
  2056.         readOnly = self.isTextArea(obj) \
  2057.                    and state.contains(pyatspi.STATE_FOCUSABLE) \
  2058.                    and not state.contains(pyatspi.STATE_EDITABLE)
  2059.         debug.println(debug.LEVEL_ALL,
  2060.                       "default.py:isReadOnlyTextArea=%s for %s" \
  2061.                       % (readOnly, debug.getAccessibleDetails(obj)))
  2062.         return readOnly
  2063.  
  2064.     def getText(self, obj, startOffset, endOffset):
  2065.         """Returns the substring of the given object's text specialization.
  2066.  
  2067.         Arguments:
  2068.         - obj: an accessible supporting the accessible text specialization
  2069.         - startOffset: the starting character position
  2070.         - endOffset: the ending character position
  2071.         """
  2072.         return obj.queryText().getText(startOffset, endOffset)
  2073.  
  2074.     def sayPhrase(self, obj, startOffset, endOffset):
  2075.         """Speaks the text of an Accessible object between the start and
  2076.         end offsets, unless the phrase is empty in which case it's ignored.
  2077.  
  2078.         Arguments:
  2079.         - obj: an Accessible object that implements the AccessibleText
  2080.                interface
  2081.         - startOffset: the start text offset.
  2082.         - endOffset: the end text offset.
  2083.         """
  2084.  
  2085.         phrase = self.getText(obj, startOffset, endOffset)
  2086.  
  2087.         if len(phrase) and phrase != "\n":
  2088.             if phrase.isupper():
  2089.                 voice = self.voices[settings.UPPERCASE_VOICE]
  2090.             else:
  2091.                 voice = self.voices[settings.DEFAULT_VOICE]
  2092.  
  2093.             phrase = self.adjustForRepeats(phrase)
  2094.             speech.speak(phrase, voice)
  2095.         else:
  2096.             # Speak blank line if appropriate.
  2097.             #
  2098.             self.sayCharacter(obj)
  2099.  
  2100.     def sayLine(self, obj):
  2101.         """Speaks the line of an AccessibleText object that contains the
  2102.         caret, unless the line is empty in which case it's ignored.
  2103.  
  2104.         Arguments:
  2105.         - obj: an Accessible object that implements the AccessibleText
  2106.                interface
  2107.         """
  2108.  
  2109.         # Get the AccessibleText interface of the provided object
  2110.         #
  2111.         [line, caretOffset, startOffset] = self.getTextLineAtCaret(obj)
  2112.         debug.println(debug.LEVEL_FINEST, \
  2113.             "sayLine: line=<%s>, len=%d, start=%d, " % \
  2114.             (line, len(line), startOffset))
  2115.         debug.println(debug.LEVEL_FINEST, \
  2116.             "caret=%d, speakBlankLines=%s" % \
  2117.             (caretOffset, settings.speakBlankLines))
  2118.  
  2119.         if len(line) and line != "\n":
  2120.             if line.isupper():
  2121.                 voice = self.voices[settings.UPPERCASE_VOICE]
  2122.             else:
  2123.                 voice = self.voices[settings.DEFAULT_VOICE]
  2124.  
  2125.             if settings.enableSpeechIndentation:
  2126.                 self.speakTextIndentation(obj, line)
  2127.             line = self.adjustForLinks(obj, line, startOffset)
  2128.             line = self.adjustForRepeats(line)
  2129.             speech.speak(line, voice)
  2130.         else:
  2131.             # Speak blank line if appropriate.
  2132.             #
  2133.             self.sayCharacter(obj)
  2134.  
  2135.     def sayWord(self, obj):
  2136.         """Speaks the word at the caret.  [[[TODO: WDW - what if there is no
  2137.         word at the caret?]]]
  2138.  
  2139.         Arguments:
  2140.         - obj: an Accessible object that implements the AccessibleText
  2141.                interface
  2142.         """
  2143.  
  2144.         text = obj.queryText()
  2145.         offset = text.caretOffset
  2146.         lastKey = orca_state.lastNonModifierKeyEvent.event_string
  2147.         lastWord = orca_state.lastWord
  2148.  
  2149.         [word, startOffset, endOffset] = \
  2150.             text.getTextAtOffset(offset,
  2151.                                  pyatspi.TEXT_BOUNDARY_WORD_START)
  2152.  
  2153.         # Speak a newline if a control-right-arrow or control-left-arrow
  2154.         # was used to cross a line boundary. Handling is different for
  2155.         # the two keys since control-right-arrow places the cursor after
  2156.         # the last character in a word, but control-left-arrow places
  2157.         # the cursor at the beginning of a word.
  2158.         #
  2159.         if lastKey == "Right" and len(lastWord) > 0:
  2160.             lastChar = lastWord[len(lastWord) - 1]
  2161.             if lastChar == "\n" and lastWord != word:
  2162.                 voice = self.voices[settings.DEFAULT_VOICE]
  2163.                 speech.speakCharacter("\n", voice)
  2164.  
  2165.         if lastKey == "Left" and len(word) > 0:
  2166.             lastChar = word[len(word) - 1]
  2167.             if lastChar == "\n" and lastWord != word:
  2168.                 voice = self.voices[settings.DEFAULT_VOICE]
  2169.                 speech.speakCharacter("\n", voice)
  2170.  
  2171.         if self.getLinkIndex(obj, offset) >= 0:
  2172.             voice = self.voices[settings.HYPERLINK_VOICE]
  2173.         elif word.isupper():
  2174.             voice = self.voices[settings.UPPERCASE_VOICE]
  2175.         else:
  2176.             voice = self.voices[settings.DEFAULT_VOICE]
  2177.  
  2178.         word = self.adjustForRepeats(word)
  2179.         orca_state.lastWord = word
  2180.         speech.speak(word, voice)
  2181.  
  2182.     def speakTextIndentation(self, obj, line):
  2183.         """Speaks a summary of the number of spaces and/or tabs at the
  2184.         beginning of the given line.
  2185.  
  2186.         Arguments:
  2187.         - obj: the text object.
  2188.         - line: the string to check for spaces and tabs.
  2189.         """
  2190.  
  2191.         # For the purpose of speaking the text indentation, replace
  2192.         # occurances of UTF-8 '\302\240' (non breaking space) with
  2193.         # spaces.
  2194.         #
  2195.         line = line.replace("\302\240",  " ")
  2196.         line = line.decode("UTF-8")
  2197.  
  2198.         spaceCount = 0
  2199.         tabCount = 0
  2200.         utterance = ""
  2201.         offset = 0
  2202.         while True:
  2203.             while (offset < len(line)) and line[offset] == ' ':
  2204.                 spaceCount += 1
  2205.                 offset += 1
  2206.             if spaceCount:
  2207.                 # Translators: this is the number of space characters on a line
  2208.                 # of text.
  2209.                 #
  2210.                 utterance += ngettext("%d space",
  2211.                                       "%d spaces",
  2212.                                       spaceCount) % spaceCount + " "
  2213.  
  2214.             while (offset < len(line)) and line[offset] == '\t':
  2215.                 tabCount += 1
  2216.                 offset += 1
  2217.             if tabCount:
  2218.                 # Translators: this is the number of tab characters on a line
  2219.                 # of text.
  2220.                 #
  2221.                 utterance += ngettext("%d tab",
  2222.                                       "%d tabs",
  2223.                                       tabCount) % tabCount + " "
  2224.  
  2225.             if not (spaceCount  or tabCount):
  2226.                 break
  2227.             spaceCount  = tabCount = 0
  2228.  
  2229.         if len(utterance):
  2230.             speech.speak(utterance)
  2231.  
  2232.     def echoPreviousSentence(self, obj):
  2233.         """Speaks the sentence prior to the caret, as long as there is
  2234.         a sentence prior to the caret and there is no intervening sentence
  2235.         delimiter between the caret and the end of the sentence.
  2236.  
  2237.         The entry condition for this method is that the character
  2238.         prior to the current caret position is a sentence delimiter,
  2239.         and it's what caused this method to be called in the first
  2240.         place.
  2241.  
  2242.         Arguments:
  2243.         - obj: an Accessible object that implements the AccessibleText
  2244.         interface.
  2245.         """
  2246.  
  2247.         try:
  2248.             text = obj.queryText()
  2249.         except NotImplementedError:
  2250.             return
  2251.  
  2252.         offset = text.caretOffset - 1
  2253.         previousOffset = text.caretOffset - 2
  2254.         if (offset < 0 or previousOffset < 0):
  2255.             return
  2256.  
  2257.         [currentChar, startOffset, endOffset] = \
  2258.             text.getTextAtOffset(offset, pyatspi.TEXT_BOUNDARY_CHAR)
  2259.         [previousChar, startOffset, endOffset] = \
  2260.             text.getTextAtOffset(previousOffset, pyatspi.TEXT_BOUNDARY_CHAR)
  2261.         if not self.isSentenceDelimiter(currentChar, previousChar):
  2262.             return
  2263.  
  2264.         # OK - we seem to be cool so far.  So...starting with what
  2265.         # should be the last character in the sentence (caretOffset - 2),
  2266.         # work our way to the beginning of the sentence, stopping when
  2267.         # we hit another sentence delimiter.
  2268.         #
  2269.         sentenceEndOffset = text.caretOffset - 2
  2270.         sentenceStartOffset = sentenceEndOffset
  2271.  
  2272.         while sentenceStartOffset >= 0:
  2273.             [currentChar, startOffset, endOffset] = \
  2274.                 text.getTextAtOffset(sentenceStartOffset,
  2275.                                      pyatspi.TEXT_BOUNDARY_CHAR)
  2276.             [previousChar, startOffset, endOffset] = \
  2277.                 text.getTextAtOffset(sentenceStartOffset-1,
  2278.                                      pyatspi.TEXT_BOUNDARY_CHAR)
  2279.             if self.isSentenceDelimiter(currentChar, previousChar):
  2280.                 break
  2281.             else:
  2282.                 sentenceStartOffset -= 1
  2283.  
  2284.         # If we came across a sentence delimiter before hitting any
  2285.         # text, we really don't have a previous sentence.
  2286.         #
  2287.         # Otherwise, get the sentence.  Remember we stopped when we
  2288.         # hit a sentence delimiter, so the sentence really starts at
  2289.         # sentenceStartOffset + 1.  getText also does not include
  2290.         # the character at sentenceEndOffset, so we need to adjust
  2291.         # for that, too.
  2292.         #
  2293.         if sentenceStartOffset == sentenceEndOffset:
  2294.             return
  2295.         else:
  2296.             sentence = self.getText(obj, sentenceStartOffset + 1,
  2297.                                          sentenceEndOffset + 1)
  2298.  
  2299.         if self.getLinkIndex(obj, sentenceStartOffset + 1) >= 0:
  2300.             voice = self.voices[settings.HYPERLINK_VOICE]
  2301.         elif sentence.isupper():
  2302.             voice = self.voices[settings.UPPERCASE_VOICE]
  2303.         else:
  2304.             voice = self.voices[settings.DEFAULT_VOICE]
  2305.  
  2306.         sentence = self.adjustForRepeats(sentence)
  2307.         speech.speak(sentence, voice)
  2308.  
  2309.     def echoPreviousWord(self, obj, offset=None):
  2310.         """Speaks the word prior to the caret, as long as there is
  2311.         a word prior to the caret and there is no intervening word
  2312.         delimiter between the caret and the end of the word.
  2313.  
  2314.         The entry condition for this method is that the character
  2315.         prior to the current caret position is a word delimiter,
  2316.         and it's what caused this method to be called in the first
  2317.         place.
  2318.  
  2319.         Arguments:
  2320.         - obj: an Accessible object that implements the AccessibleText
  2321.                interface.
  2322.         - offset: if not None, the offset within the text to use as the
  2323.                   end of the word.
  2324.         """
  2325.  
  2326.         try:
  2327.             text = obj.queryText()
  2328.         except NotImplementedError:
  2329.             return
  2330.  
  2331.         if not offset:
  2332.             offset = text.caretOffset - 1
  2333.         if (offset < 0):
  2334.             return
  2335.  
  2336.         [char, startOffset, endOffset] = \
  2337.             text.getTextAtOffset( \
  2338.                 offset,
  2339.                 pyatspi.TEXT_BOUNDARY_CHAR)
  2340.         if not self.isWordDelimiter(char):
  2341.             return
  2342.  
  2343.         # OK - we seem to be cool so far.  So...starting with what
  2344.         # should be the last character in the word (caretOffset - 2),
  2345.         # work our way to the beginning of the word, stopping when
  2346.         # we hit another word delimiter.
  2347.         #
  2348.         wordEndOffset = offset - 1
  2349.         wordStartOffset = wordEndOffset
  2350.  
  2351.         while wordStartOffset >= 0:
  2352.             [char, startOffset, endOffset] = \
  2353.                 text.getTextAtOffset( \
  2354.                     wordStartOffset,
  2355.                     pyatspi.TEXT_BOUNDARY_CHAR)
  2356.             if self.isWordDelimiter(char):
  2357.                 break
  2358.             else:
  2359.                 wordStartOffset -= 1
  2360.  
  2361.         # If we came across a word delimiter before hitting any
  2362.         # text, we really don't have a previous word.
  2363.         #
  2364.         # Otherwise, get the word.  Remember we stopped when we
  2365.         # hit a word delimiter, so the word really starts at
  2366.         # wordStartOffset + 1.  getText also does not include
  2367.         # the character at wordEndOffset, so we need to adjust
  2368.         # for that, too.
  2369.         #
  2370.         if wordStartOffset == wordEndOffset:
  2371.             return
  2372.         else:
  2373.             word = self.getText(obj, wordStartOffset + 1, wordEndOffset + 1)
  2374.  
  2375.         if self.getLinkIndex(obj, wordStartOffset + 1) >= 0:
  2376.             voice = self.voices[settings.HYPERLINK_VOICE]
  2377.         elif word.isupper():
  2378.             voice = self.voices[settings.UPPERCASE_VOICE]
  2379.         else:
  2380.             voice = self.voices[settings.DEFAULT_VOICE]
  2381.  
  2382.         word = self.adjustForRepeats(word)
  2383.         speech.speak(word, voice)
  2384.  
  2385.     def sayCharacter(self, obj):
  2386.         """Speak the character at the caret.
  2387.  
  2388.         Arguments:
  2389.         - obj: an Accessible object that implements the AccessibleText
  2390.                interface
  2391.         """
  2392.  
  2393.         text = obj.queryText()
  2394.         offset = text.caretOffset
  2395.  
  2396.         # If we have selected text and the last event was a move to the
  2397.         # right, then speak the character to the left of where the text
  2398.         # caret is (i.e. the selected character).
  2399.         #
  2400.         try:
  2401.             mods = orca_state.lastInputEvent.modifiers
  2402.             eventString = orca_state.lastInputEvent.event_string
  2403.         except:
  2404.             mods = 0
  2405.             eventString = ""
  2406.  
  2407.         if (mods & settings.SHIFT_MODIFIER_MASK) \
  2408.            and eventString in ["Right", "Down"]:
  2409.             offset -= 1
  2410.  
  2411.         character, startOffset, endOffset = \
  2412.             text.getTextAtOffset(offset, pyatspi.TEXT_BOUNDARY_CHAR)
  2413.         if not character:
  2414.             character = "\n"
  2415.  
  2416.         if self.getLinkIndex(obj, offset) >= 0:
  2417.             voice = self.voices[settings.HYPERLINK_VOICE]
  2418.         elif character.decode("UTF-8").isupper():
  2419.             voice = self.voices[settings.UPPERCASE_VOICE]
  2420.         else:
  2421.             voice = self.voices[settings.DEFAULT_VOICE]
  2422.  
  2423.         debug.println(debug.LEVEL_FINEST, \
  2424.             "sayCharacter: char=<%s>, startOffset=%d, " % \
  2425.             (character, startOffset))
  2426.         debug.println(debug.LEVEL_FINEST, \
  2427.             "caretOffset=%d, endOffset=%d, speakBlankLines=%s" % \
  2428.             (offset, endOffset, settings.speakBlankLines))
  2429.  
  2430.         if character == "\n":
  2431.             line = text.getTextAtOffset(max(0, offset),
  2432.                                         pyatspi.TEXT_BOUNDARY_LINE_START)
  2433.             if not line[0] or line[0] == "\n":
  2434.                 # This is a blank line. Announce it if the user requested
  2435.                 # that blank lines be spoken.
  2436.                 if settings.speakBlankLines:
  2437.                     # Translators: "blank" is a short word to mean the
  2438.                     # user has navigated to an empty line.
  2439.                     #
  2440.                     speech.speak(_("blank"), voice, False)
  2441.                 return
  2442.  
  2443.         speech.speakCharacter(character, voice)
  2444.  
  2445.     def isFunctionalDialog(self, obj):
  2446.         """Returns true if the window is a functioning as a dialog.
  2447.         This method should be subclassed by application scripts as needed.
  2448.         """
  2449.  
  2450.         return False
  2451.  
  2452.     def getUnfocusedAlertAndDialogCount(self, obj):
  2453.         """If the current application has one or more alert or dialog
  2454.         windows and the currently focused window is not an alert or a dialog,
  2455.         return a count of the number of alert and dialog windows, otherwise
  2456.         return a count of zero.
  2457.  
  2458.         Arguments:
  2459.         - obj: the Accessible object
  2460.  
  2461.         Returns the alert and dialog count.
  2462.         """
  2463.  
  2464.         alertAndDialogCount = 0
  2465.         app = obj.getApplication()
  2466.         window = self.getTopLevel(obj)
  2467.         if window and window.getRole() != pyatspi.ROLE_ALERT and \
  2468.            window.getRole() != pyatspi.ROLE_DIALOG and \
  2469.            not self.isFunctionalDialog(window):
  2470.             for child in app:
  2471.                 if child.getRole() == pyatspi.ROLE_ALERT or \
  2472.                    child.getRole() == pyatspi.ROLE_DIALOG or \
  2473.                    self.isFunctionalDialog(child):
  2474.                     alertAndDialogCount += 1
  2475.  
  2476.         return alertAndDialogCount
  2477.  
  2478.     def presentTooltip(self, obj):
  2479.         """
  2480.         Speaks the tooltip for the current object of interest.
  2481.         """
  2482.  
  2483.         # The tooltip is generally the accessible description. If
  2484.         # the description is not set, present the text that is
  2485.         # spoken when the object receives keyboard focus.
  2486.         #
  2487.         text = ""
  2488.         if obj.description:
  2489.             text = obj.description
  2490.         else:
  2491.             # Reuse the "where am I" algorithm.
  2492.             text = self.whereAmI.getObjLabelAndName(obj)
  2493.  
  2494.         debug.println(debug.LEVEL_FINEST, "presentTooltip: text='%s'" % text)
  2495.         if text != "":
  2496.             braille.displayMessage(text)
  2497.             speech.speak(text)
  2498.  
  2499.     def doWhereAmI(self, inputEvent, basicOnly):
  2500.         """Peforms the whereAmI operation.
  2501.  
  2502.         Arguments:
  2503.         - inputEvent:     The original inputEvent
  2504.         """
  2505.  
  2506.         obj = orca_state.locusOfFocus
  2507.         self.updateBraille(obj)
  2508.  
  2509.         return self.whereAmI.whereAmI(obj, basicOnly)
  2510.  
  2511.     def whereAmIBasic(self, inputEvent):
  2512.         """Speaks basic information about the current object of interest.
  2513.         """
  2514.  
  2515.         self.doWhereAmI(inputEvent, True)
  2516.  
  2517.     def whereAmIDetailed(self, inputEvent):
  2518.         """Speaks detailed/custom information about the current object of
  2519.         interest.
  2520.         """
  2521.  
  2522.         self.doWhereAmI(inputEvent, False)
  2523.  
  2524.     def getTitle(self, inputEvent):
  2525.         """Speaks the title of the window with focus.
  2526.         """
  2527.  
  2528.         obj = orca_state.locusOfFocus
  2529.         self.updateBraille(obj)
  2530.  
  2531.         return self.whereAmI.speakTitle(orca_state.locusOfFocus)
  2532.  
  2533.     def getStatusBar(self, inputEvent):
  2534.         """Speaks the contents of the status bar of the window with focus.
  2535.         """
  2536.  
  2537.         obj = orca_state.locusOfFocus
  2538.         self.updateBraille(obj)
  2539.  
  2540.         return self.whereAmI.speakStatusBar(orca_state.locusOfFocus)
  2541.  
  2542.     def findCommonAncestor(self, a, b):
  2543.         """Finds the common ancestor between Accessible a and Accessible b.
  2544.  
  2545.         Arguments:
  2546.         - a: Accessible
  2547.         - b: Accessible
  2548.         """
  2549.  
  2550.         debug.println(debug.LEVEL_FINEST,
  2551.                       "default.findCommonAncestor...")
  2552.  
  2553.         if (not a) or (not b):
  2554.             return None
  2555.  
  2556.         if a == b:
  2557.             return a
  2558.  
  2559.         aParents = [a]
  2560.         try:
  2561.             parent = a.parent
  2562.             while parent and (parent.parent != parent):
  2563.                 aParents.append(parent)
  2564.                 parent = parent.parent
  2565.             aParents.reverse()
  2566.         except:
  2567.             debug.printException(debug.LEVEL_FINEST)
  2568.  
  2569.         bParents = [b]
  2570.         try:
  2571.             parent = b.parent
  2572.             while parent and (parent.parent != parent):
  2573.                 bParents.append(parent)
  2574.                 parent = parent.parent
  2575.             bParents.reverse()
  2576.         except:
  2577.             debug.printException(debug.LEVEL_FINEST)
  2578.  
  2579.         commonAncestor = None
  2580.  
  2581.         maxSearch = min(len(aParents), len(bParents))
  2582.         i = 0
  2583.         while i < maxSearch:
  2584.             if self.isSameObject(aParents[i], bParents[i]):
  2585.                 commonAncestor = aParents[i]
  2586.                 i += 1
  2587.             else:
  2588.                 break
  2589.  
  2590.         debug.println(debug.LEVEL_FINEST,
  2591.                       "...default.findCommonAncestor")
  2592.  
  2593.         return commonAncestor
  2594.  
  2595.     def handleProgressBarUpdate(self, event, obj):
  2596.         """Determine whether this progress bar event should be spoken or not.
  2597.         It should be spoken if:
  2598.         1/ settings.enableProgressBarUpdates is True.
  2599.         2/ The application with the progress bar has focus.
  2600.         3/ The time of this event exceeds the
  2601.            settings.progressBarUpdateInterval value.  This value
  2602.            indicates the time (in seconds) between potential spoken
  2603.            progress bar updates.
  2604.         4/ The new value of the progress bar (converted to an integer),
  2605.            is different from the last one or equals 100 (i.e complete).
  2606.  
  2607.         Arguments:
  2608.         - event: if not None, the Event that caused this to happen
  2609.         - obj:  the Accessible progress bar object.
  2610.         """
  2611.  
  2612.         if settings.enableProgressBarUpdates:
  2613.             if orca_state.locusOfFocus and \
  2614.                orca_state.locusOfFocus.getApplication() == obj.getApplication():
  2615.                 currentTime = time.time()
  2616.  
  2617.                 # Check for defunct progress bars. Get rid of them if they
  2618.                 # are all defunct. Also find out which progress bar was
  2619.                 # the most recently updated.
  2620.                 #
  2621.                 defunctBars = 0
  2622.                 mostRecentUpdate = [obj, 0]
  2623.                 for key, value in self.lastProgressBarTime.items():
  2624.                     if value > mostRecentUpdate[1]:
  2625.                         mostRecentUpdate = [key, value]
  2626.                     try:
  2627.                         isDefunct = \
  2628.                             key.getState().contains(pyatspi.STATE_DEFUNCT)
  2629.                     except:
  2630.                         isDefunct = True
  2631.                     if isDefunct:
  2632.                         defunctBars += 1
  2633.  
  2634.                 if defunctBars == len(self.lastProgressBarTime):
  2635.                     self.lastProgressBarTime = {}
  2636.                     self.lastProgressBarValue = {}
  2637.  
  2638.                 # If this progress bar is not already known, create initial
  2639.                 # values for it.
  2640.                 #
  2641.                 if obj not in self.lastProgressBarTime:
  2642.                     self.lastProgressBarTime[obj] = 0.0
  2643.                 if obj not in self.lastProgressBarValue:
  2644.                     self.lastProgressBarValue[obj] = None
  2645.  
  2646.                 lastProgressBarTime = self.lastProgressBarTime[obj]
  2647.                 lastProgressBarValue = self.lastProgressBarValue[obj]
  2648.                 value = obj.queryValue()
  2649.                 percentValue = int((value.currentValue / \
  2650.                     (value.maximumValue - value.minimumValue)) * 100.0)
  2651.  
  2652.                 if (currentTime - lastProgressBarTime) > \
  2653.                        settings.progressBarUpdateInterval \
  2654.                    or percentValue == 100:
  2655.                     if lastProgressBarValue != percentValue:
  2656.                         utterances = []
  2657.  
  2658.                         # There may be cases when more than one progress
  2659.                         # bar is updating at the same time in a window.
  2660.                         # If this is the case, then speak the index of this
  2661.                         # progress bar in the dictionary of known progress
  2662.                         # bars, as well as the value. But only speak the
  2663.                         # index if this progress bar was not the most
  2664.                         # recently updated to prevent chattiness.
  2665.                         #
  2666.                         if len(self.lastProgressBarTime) > 1:
  2667.                             index = 0
  2668.                             for key in self.lastProgressBarTime.keys():
  2669.                                 if key == obj and key != mostRecentUpdate[0]:
  2670.                                     # Translators: this is an index value
  2671.                                     # so that we can tell which progress bar
  2672.                                     # we are referring to.
  2673.                                     #
  2674.                                     label = _("Progress bar %d.") % (index + 1)
  2675.                                     utterances.append(label)
  2676.                                 else:
  2677.                                     index += 1
  2678.  
  2679.                         # Translators: this is the percentage value of a
  2680.                         # progress bar.
  2681.                         #
  2682.                         percentage = _("%d percent.") % percentValue + " "
  2683.  
  2684.                         utterances.append(percentage)
  2685.                         speech.speakUtterances(utterances)
  2686.  
  2687.                         self.lastProgressBarTime[obj] = currentTime
  2688.                         self.lastProgressBarValue[obj] = percentValue
  2689.  
  2690.     def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus):
  2691.         """Called when the visual object with focus changes.
  2692.  
  2693.         Arguments:
  2694.         - event: if not None, the Event that caused the change
  2695.         - oldLocusOfFocus: Accessible that is the old locus of focus
  2696.         - newLocusOfFocus: Accessible that is the new locus of focus
  2697.         """
  2698.  
  2699.         if newLocusOfFocus \
  2700.            and newLocusOfFocus.getState().contains(pyatspi.STATE_DEFUNCT):
  2701.             return
  2702.  
  2703.         try:
  2704.             if self.findCommandRun:
  2705.                 # Then the Orca Find dialog has just given up focus
  2706.                 # to the original window.  We don't want to speak
  2707.                 # the window title, current line, etc.
  2708.                 return
  2709.         except:
  2710.             pass
  2711.  
  2712.         if newLocusOfFocus:
  2713.             mag.magnifyAccessible(event, newLocusOfFocus)
  2714.  
  2715.         # We always automatically go back to focus tracking mode when
  2716.         # the focus changes.
  2717.         #
  2718.         if self.flatReviewContext:
  2719.             self.toggleFlatReviewMode()
  2720.  
  2721.         # [[[TODO: WDW - HACK because parents that manage their descendants
  2722.         # can give us a different object each time we ask for the same
  2723.         # exact child.  So...we do a check here to see if the old object
  2724.         # and new object have the same index in the parent and if they
  2725.         # have the same name.  If so, then they are likely to be the same
  2726.         # object.  The reason we check for the name here is a small sanity
  2727.         # check.  This whole algorithm could fail because one might be
  2728.         # deleting/adding identical elements from/to a list or table, thus
  2729.         # the objects really could be different even though they seem the
  2730.         # same.  Logged as bug 319675.]]]
  2731.         #
  2732.         if self.isSameObject(oldLocusOfFocus, newLocusOfFocus):
  2733.             return
  2734.  
  2735.         # Well...now that we got that behind us, let's do what we're supposed
  2736.         # to do.
  2737.         #
  2738.         if oldLocusOfFocus:
  2739.             oldParent = oldLocusOfFocus.parent
  2740.         else:
  2741.             oldParent = None
  2742.  
  2743.         if newLocusOfFocus:
  2744.             newParent = newLocusOfFocus.parent
  2745.         else:
  2746.             newParent = None
  2747.  
  2748.         # Clear the point of reference.
  2749.         # If the point of reference is a cell, we want to keep the
  2750.         # table-related points of reference.
  2751.         if oldParent is not None and oldParent == newParent and \
  2752.               newParent.getRole() == pyatspi.ROLE_TABLE:
  2753.             for key in self.pointOfReference.keys():
  2754.                 if key not in ('lastRow', 'lastColumn'):
  2755.                     del self.pointOfReference[key]
  2756.         else:
  2757.             self.pointOfReference = {}
  2758.  
  2759.         if newLocusOfFocus:
  2760.             self.updateBraille(newLocusOfFocus)
  2761.  
  2762.             utterances = []
  2763.  
  2764.             # Now figure out how of the container context changed and
  2765.             # speech just what is different.
  2766.             #
  2767.             commonAncestor = self.findCommonAncestor(oldLocusOfFocus,
  2768.                                                      newLocusOfFocus)
  2769.             if commonAncestor:
  2770.                 context = self.speechGenerator.getSpeechContext( \
  2771.                                            newLocusOfFocus, commonAncestor)
  2772.                 utterances.append(" ".join(context))
  2773.  
  2774.             # Now, we'll treat table row and column headers as context
  2775.             # as well.  This requires special handling because we're
  2776.             # making headers seem hierarchical in the context, but they
  2777.             # are not hierarchical in the containment hierarchicy.
  2778.             # We also only want to speak the one that changed.  If both
  2779.             # changed, first speak the row header, then the column header.
  2780.             #
  2781.             # We also keep track of tree level depth and only announce
  2782.             # that if it changes.
  2783.             #
  2784.             # Note that Java Swing allows things like ROLE_LABEL objects
  2785.             # in trees and tables, so we'll check the parent's role to 
  2786.             # see if it is a table.
  2787.             #
  2788.             oldNodeLevel = -1
  2789.             newNodeLevel = -1
  2790.             if (newLocusOfFocus.getRole() == pyatspi.ROLE_TABLE_CELL) \
  2791.                or (newParent.getRole() == pyatspi.ROLE_TABLE):
  2792.                 try:
  2793.                     table = oldParent.queryTable()
  2794.                 except:
  2795.                     table = None
  2796.                 if table and \
  2797.                       ((oldLocusOfFocus.getRole() == pyatspi.ROLE_TABLE_CELL) \
  2798.                        or (oldParent.getRole() == pyatspi.ROLE_TABLE)):
  2799.                     index = self.getCellIndex(oldLocusOfFocus)
  2800.                     oldRow = table.getRowAtIndex(index)
  2801.                     oldCol = table.getColumnAtIndex(index)
  2802.                 else:
  2803.                     oldRow = -1
  2804.                     oldCol = -1
  2805.  
  2806.                 try:
  2807.                     table = newParent.queryTable()
  2808.                 except:
  2809.                     pass
  2810.                 else:
  2811.                     index = self.getCellIndex(newLocusOfFocus)
  2812.                     newRow = table.getRowAtIndex(index)
  2813.                     newCol = table.getColumnAtIndex(index)
  2814.  
  2815.                     if (newRow >= 0) \
  2816.                        and ((newRow != oldRow) or (oldParent != newParent)):
  2817.                         # Get the header information.  In Java Swing, the
  2818.                         # information is not exposed via the description
  2819.                         # but is instead a header object, so we fall back
  2820.                         # to that if it exists.
  2821.                         #
  2822.                         # [[[TODO: WDW - the more correct thing to do, I 
  2823.                         # think, is to look at the row header object.
  2824.                         # We've been looking at the description for so 
  2825.                         # long, though, that we'll give the description 
  2826.                         # preference for now.]]]
  2827.                         #
  2828.                         desc = table.getRowDescription(newRow)
  2829.                         if not desc:
  2830.                             header = table.getRowHeader(newRow)
  2831.                             if header:
  2832.                                 desc = self.getDisplayedText(header)
  2833.                         if desc and len(desc):
  2834.                             text = desc
  2835.                             if settings.speechVerbosityLevel \
  2836.                                    == settings.VERBOSITY_LEVEL_VERBOSE:
  2837.                                 text += " " \
  2838.                                         + rolenames.rolenames[\
  2839.                                         pyatspi.ROLE_ROW_HEADER].speech
  2840.                             utterances.append(text)
  2841.                     if (newCol >= 0) \
  2842.                        and ((newCol != oldCol) or (oldParent != newParent)):
  2843.                         # Don't speak Thunderbird column headers, since
  2844.                         # it's not possible to navigate across a row.
  2845.                         topName = self.getTopLevelName(newLocusOfFocus)
  2846.                         if not topName.endswith(" - Thunderbird"):
  2847.                             # Get the header information.  In Java Swing, the
  2848.                             # information is not exposed via the description
  2849.                             # but is instead a header object, so we fall back
  2850.                             # to that if it exists.
  2851.                             #
  2852.                             # [[[TODO: WDW - the more correct thing to do, I 
  2853.                             # think, is to look at the row header object.
  2854.                             # We've been looking at the description for so 
  2855.                             # long, though, that we'll give the description 
  2856.                             # preference for now.]]]
  2857.                             #
  2858.                             desc = table.getColumnDescription(newCol)
  2859.                             if not desc:
  2860.                                 header = table.getColumnHeader(newCol)
  2861.                                 if header:
  2862.                                     desc = self.getDisplayedText(header)
  2863.                             cellText = self.getDisplayedText(newLocusOfFocus)
  2864.                             if desc and len(desc) and cellText != desc:
  2865.                                 text = desc
  2866.                                 if settings.speechVerbosityLevel \
  2867.                                        == settings.VERBOSITY_LEVEL_VERBOSE:
  2868.                                     text += " " \
  2869.                                             + rolenames.rolenames[\
  2870.                                             pyatspi.ROLE_COLUMN_HEADER].speech
  2871.                                 utterances.append(text)
  2872.  
  2873.             oldNodeLevel = self.getNodeLevel(oldLocusOfFocus)
  2874.             newNodeLevel = self.getNodeLevel(newLocusOfFocus)
  2875.  
  2876.             # We'll also treat radio button groups as though they are
  2877.             # in a context, with the label for the group being the
  2878.             # name of the context.
  2879.             #
  2880.             if newLocusOfFocus \
  2881.                and newLocusOfFocus.getRole() == pyatspi.ROLE_RADIO_BUTTON:
  2882.                 radioGroupLabel = None
  2883.                 inSameGroup = False
  2884.                 relations = newLocusOfFocus.getRelationSet()
  2885.                 for relation in relations:
  2886.                     if (not radioGroupLabel) \
  2887.                         and (relation.getRelationType() \
  2888.                              == pyatspi.RELATION_LABELLED_BY):
  2889.                         radioGroupLabel = relation.getTarget(0)
  2890.                     if (not inSameGroup) \
  2891.                         and (relation.getRelationType() \
  2892.                              == pyatspi.RELATION_MEMBER_OF):
  2893.                         for i in range(0, relation.getNTargets()):
  2894.                             target = relation.getTarget(i)
  2895.                             if target == oldLocusOfFocus:
  2896.                                 inSameGroup = True
  2897.                                 break
  2898.  
  2899.                 # We'll only announce the radio button group when we
  2900.                 # switch groups.
  2901.                 #
  2902.                 if (not inSameGroup) and radioGroupLabel:
  2903.                     utterances.append(self.getDisplayedText(radioGroupLabel))
  2904.  
  2905.             # Check to see if we are in the Pronunciation Dictionary in the
  2906.             # Orca Preferences dialog. If so, then we do not want to use the
  2907.             # pronunciation dictionary to replace the actual words in the
  2908.             # first column of this table.
  2909.             #
  2910.             rolesList = [pyatspi.ROLE_TABLE_CELL, \
  2911.                          pyatspi.ROLE_TABLE, \
  2912.                          pyatspi.ROLE_SCROLL_PANE, \
  2913.                          pyatspi.ROLE_PANEL, \
  2914.                          pyatspi.ROLE_PANEL]
  2915.             if self.isDesiredFocusedItem(newLocusOfFocus, rolesList) and \
  2916.                newLocusOfFocus.getApplication().name == "orca":
  2917.                 orca_state.usePronunciationDictionary = False
  2918.             else:
  2919.                 orca_state.usePronunciationDictionary = True
  2920.  
  2921.             # Get the text for the object itself.
  2922.             #
  2923.             utterances.extend(
  2924.                 self.speechGenerator.getSpeech(newLocusOfFocus, False))
  2925.             utterances.extend(
  2926.                 self.tutorialGenerator.getTutorial(newLocusOfFocus, False))
  2927.             # Now speak the new tree node level if it has changed.
  2928.             #
  2929.             if (oldNodeLevel != newNodeLevel) \
  2930.                and (newNodeLevel >= 0):
  2931.                 # Translators: this represents the depth of a node in a tree
  2932.                 # view (i.e., how many ancestors a node has).
  2933.                 #
  2934.                 utterances.append(_("tree level %d") % (newNodeLevel + 1))
  2935.  
  2936.             # If this is an icon within an layered pane or a table cell
  2937.             # within a table or a tree table and the item is focused but not
  2938.             # selected, let the user know. See bug #486908 for more details.
  2939.             #
  2940.             checkIfSelected = False
  2941.             objRole, parentRole, state = None, None, None
  2942.             if newLocusOfFocus:
  2943.                 objRole = newLocusOfFocus.getRole()
  2944.                 state = newLocusOfFocus.getState()
  2945.                 if newLocusOfFocus.parent:
  2946.                     parentRole = newLocusOfFocus.parent.getRole()
  2947.  
  2948.             if objRole == pyatspi.ROLE_TABLE_CELL and \
  2949.                (parentRole == pyatspi.ROLE_TREE_TABLE or \
  2950.                 parentRole == pyatspi.ROLE_TABLE):
  2951.                 checkIfSelected = True
  2952.  
  2953.             # If we met the last set of conditions, but we got here by
  2954.             # moving left or right on the same row, then don't announce the
  2955.             # selection state to the user. See bug #523235 for more details.
  2956.             #
  2957.             if checkIfSelected == True and \
  2958.                (orca_state.lastNonModifierKeyEvent and \
  2959.                (orca_state.lastNonModifierKeyEvent.event_string == "Left" or \
  2960.                orca_state.lastNonModifierKeyEvent.event_string == "Right")):
  2961.                 checkIfSelected = False
  2962.  
  2963.             if objRole == pyatspi.ROLE_ICON and \
  2964.                 parentRole == pyatspi.ROLE_LAYERED_PANE:
  2965.                 checkIfSelected = True
  2966.  
  2967.             if checkIfSelected and state \
  2968.                and not state.contains(pyatspi.STATE_SELECTED):
  2969.                 # Translators: this is in reference to a table cell being
  2970.                 # selected or not.
  2971.                 #
  2972.                 utterances.append(C_("tablecell", " not selected"))
  2973.  
  2974.             # We might be automatically speaking the unbound labels
  2975.             # in a dialog box as the result of the dialog box suddenly
  2976.             # appearing.  If so, don't interrupt this because of a
  2977.             # focus event that occurs when something like the "OK"
  2978.             # button gets focus shortly after the window appears.
  2979.             #
  2980.             shouldNotInterrupt = (event and event.type.startswith("focus:")) \
  2981.                 and self.windowActivateTime \
  2982.                 and ((time.time() - self.windowActivateTime) < 1.0)
  2983.  
  2984.             if objRole == pyatspi.ROLE_LINK:
  2985.                 voice = self.voices[settings.HYPERLINK_VOICE]
  2986.             else:
  2987.                 voice = self.voices[settings.DEFAULT_VOICE]
  2988.  
  2989.             speech.speakUtterances(utterances, voice, not shouldNotInterrupt)
  2990.  
  2991.             # If this is a table cell, save the current row and column
  2992.             # information in the table cell's table, so that we can use
  2993.             # it the next time.
  2994.             #
  2995.             if objRole == pyatspi.ROLE_TABLE_CELL:
  2996.                 try:
  2997.                     table = newParent.queryTable()
  2998.                 except:
  2999.                     pass
  3000.                 else:
  3001.                     index = self.getCellIndex(newLocusOfFocus)
  3002.                     column = table.getColumnAtIndex(index)
  3003.                     self.pointOfReference['lastColumn'] = column
  3004.                     row = table.getRowAtIndex(index)
  3005.                     self.pointOfReference['lastRow'] = row
  3006.         else:
  3007.             orca_state.noFocusTimeStamp = time.time()
  3008.  
  3009.     def visualAppearanceChanged(self, event, obj):
  3010.         """Called when the visual appearance of an object changes.  This
  3011.         method should not be called for objects whose visual appearance
  3012.         changes solely because of focus -- setLocusOfFocus is used for that.
  3013.         Instead, it is intended mostly for objects whose notional 'value' has
  3014.         changed, such as a checkbox changing state, a progress bar advancing,
  3015.         a slider moving, text inserted, caret moved, etc.
  3016.  
  3017.         Arguments:
  3018.         - event: if not None, the Event that caused this to happen
  3019.         - obj: the Accessible whose visual appearance changed.
  3020.         """
  3021.         # Check if this event is for a progress bar.
  3022.         #
  3023.         if obj.getRole() == pyatspi.ROLE_PROGRESS_BAR:
  3024.             self.handleProgressBarUpdate(event, obj)
  3025.  
  3026.         if self.flatReviewContext:
  3027.             if self.isSameObject(
  3028.                 obj,
  3029.                 self.flatReviewContext.getCurrentAccessible()):
  3030.                 self.updateBrailleReview()
  3031.             return
  3032.  
  3033.         # We care if panels are suddenly showing.  The reason for this
  3034.         # is that some applications, such as Evolution, will bring up
  3035.         # a wizard dialog that uses "Forward" and "Backward" buttons
  3036.         # that change the contents of the dialog.  We only discover
  3037.         # this through showing events. [[[TODO: WDW - perhaps what we
  3038.         # really want is to speak unbound labels that are suddenly
  3039.         # showing?  event.detail == 1 means object is showing.]]]
  3040.         #
  3041.         # [[[TODO: WDW - I added the 'False' condition to prevent this
  3042.         # condition from ever working.  I wanted to keep the code around,
  3043.         # though, just in case we want to reuse it somewhere else.  The
  3044.         # bug that spurred all of this on is:
  3045.         #
  3046.         #    http://bugzilla.gnome.org/show_bug.cgi?id=338687
  3047.         #
  3048.         # The main problem is that the profile editor in gnome-terminal
  3049.         # ended up being very verbose and speaking lots of things it
  3050.         # should not have been speaking.]]]
  3051.         #
  3052.         if False and (obj.getRole() == pyatspi.ROLE_PANEL) \
  3053.                and (event.detail1 == 1) \
  3054.                and self.isInActiveApp(obj):
  3055.  
  3056.             # It's only showing if its parent is showing. [[[TODO: WDW -
  3057.             # HACK we stop at the application level because applications
  3058.             # never seem to have their showing state set.]]]
  3059.             #
  3060.             reallyShowing = True
  3061.             parent = obj.parent
  3062.             while reallyShowing \
  3063.                       and parent \
  3064.                       and (parent != parent.parent) \
  3065.                       and (parent.getRole() != pyatspi.ROLE_APPLICATION):
  3066.                 debug.println(debug.LEVEL_FINEST,
  3067.                               "default.visualAppearanceChanged - " \
  3068.                               + "checking parent")
  3069.                 reallyShowing = parent.getState().contains( \
  3070.                                                   pyatspi.STATE_SHOWING)
  3071.                 parent = parent.parent
  3072.  
  3073.             # Find all the unrelated labels in the dialog and speak them.
  3074.             #
  3075.             if reallyShowing:
  3076.                 utterances = []
  3077.                 labels = self.findUnrelatedLabels(obj)
  3078.                 for label in labels:
  3079.                     utterances.append(label.name)
  3080.  
  3081.                 speech.speakUtterances(utterances)
  3082.  
  3083.                 return
  3084.  
  3085.         # If this object is CONTROLLED_BY the object that currently
  3086.         # has focus, speak/braille this object.
  3087.         #
  3088.         relations = obj.getRelationSet()
  3089.         for relation in relations:
  3090.             if relation.getRelationType() \
  3091.                    == pyatspi.RELATION_CONTROLLED_BY:
  3092.                 target = relation.getTarget(0)
  3093.                 if target == orca_state.locusOfFocus:
  3094.                     self.updateBraille(target)
  3095.                     utterances = self.speechGenerator.getSpeech(target, True)
  3096.                     utterances.extend(self.tutorialGenerator.getTutorial(
  3097.                                target, True))
  3098.                     speech.speakUtterances(utterances)
  3099.                     return
  3100.  
  3101.         # If this object is a label, and if it has a LABEL_FOR relation
  3102.         # to the focused object, then we should speak/braille the
  3103.         # focused object, as if it had just got focus.
  3104.         #
  3105.         if obj.getRole() == pyatspi.ROLE_LABEL \
  3106.            and obj.getState().contains(pyatspi.STATE_SHOWING):
  3107.             for relation in relations:
  3108.                 if relation.getRelationType() \
  3109.                        == pyatspi.RELATION_LABEL_FOR:
  3110.                     target = relation.getTarget(0)
  3111.                     if target == orca_state.locusOfFocus:
  3112.                         self.updateBraille(target)
  3113.                         utterances = self.speechGenerator.getSpeech(
  3114.                                      target, True)
  3115.                         utterances.extend(self.tutorialGenerator.getTutorial(
  3116.                                           target, True))
  3117.                         speech.speakUtterances(utterances)
  3118.                         return
  3119.  
  3120.         if not self.isSameObject(obj, orca_state.locusOfFocus):
  3121.             return
  3122.  
  3123.         # Radio buttons normally change their state when you arrow to them,
  3124.         # so we handle the announcement of their state changes in the focus
  3125.         # handling code.  However, we do need to handle radio buttons where
  3126.         # the user needs to press the space key so select them.  We see this
  3127.         # in the disk selection area of the OpenSolaris gui-install application
  3128.         # for example.
  3129.         #
  3130.         if obj.getRole() == pyatspi.ROLE_RADIO_BUTTON:
  3131.             if orca_state.lastNonModifierKeyEvent \
  3132.                and orca_state.lastNonModifierKeyEvent.event_string == "space":
  3133.                 pass
  3134.             else:
  3135.                 return
  3136.  
  3137.         if event:
  3138.             debug.println(debug.LEVEL_FINE,
  3139.                           "VISUAL CHANGE: '%s' '%s' (event='%s')" \
  3140.                           % (obj.name, obj.getRole(), event.type))
  3141.         else:
  3142.             debug.println(debug.LEVEL_FINE,
  3143.                           "VISUAL CHANGE: '%s' '%s' (event=None)" \
  3144.                           % (obj.name, obj.getRole()))
  3145.  
  3146.         mag.magnifyAccessible(event, obj)
  3147.         self.updateBraille(obj)
  3148.         utterances = self.speechGenerator.getSpeech(obj, True)
  3149.         utterances.extend(self.tutorialGenerator.getTutorial(obj, True))
  3150.         speech.speakUtterances(utterances)
  3151.  
  3152.     def updateBraille(self, obj, extraRegion=None):
  3153.         """Updates the braille display to show the give object.
  3154.  
  3155.         Arguments:
  3156.         - obj: the Accessible
  3157.         - extra: extra Region to add to the end
  3158.         """
  3159.  
  3160.         if not obj:
  3161.             return
  3162.  
  3163.         braille.clear()
  3164.  
  3165.         line = braille.Line()
  3166.         braille.addLine(line)
  3167.  
  3168.         # For multiline text areas, we only show the context if we
  3169.         # are on the very first line.  Otherwise, we show only the
  3170.         # line.
  3171.         #
  3172.         try:
  3173.             text = obj.queryText()
  3174.         except NotImplementedError:
  3175.             text = None
  3176.         if text and self.isTextArea(obj):
  3177.             [lineString, startOffset, endOffset] = text.getTextAtOffset(
  3178.                 text.caretOffset,
  3179.                 pyatspi.TEXT_BOUNDARY_LINE_START)
  3180.             if startOffset == 0:
  3181.                 line.addRegions(self.brailleGenerator.getBrailleContext(obj))
  3182.         else:
  3183.             line.addRegions(self.brailleGenerator.getBrailleContext(obj))
  3184.  
  3185.         result = self.brailleGenerator.getBrailleRegions(obj)
  3186.         line.addRegions(result[0])
  3187.  
  3188.         if extraRegion:
  3189.             line.addRegion(extraRegion)
  3190.  
  3191.         if extraRegion:
  3192.             braille.setFocus(extraRegion)
  3193.         else:
  3194.             braille.setFocus(result[1])
  3195.  
  3196.         braille.refresh(True)
  3197.  
  3198.     ########################################################################
  3199.     #                                                                      #
  3200.     # AT-SPI OBJECT EVENT HANDLERS                                         #
  3201.     #                                                                      #
  3202.     ########################################################################
  3203.  
  3204.     def onFocus(self, event):
  3205.         """Called whenever an object gets focus.
  3206.  
  3207.         Arguments:
  3208.         - event: the Event
  3209.         """
  3210.  
  3211.         # [[[TODO: WDW - HACK to deal with quirky GTK+ menu behavior.
  3212.         # The problem is that when moving to submenus in a menu, the
  3213.         # menu gets focus first and then the submenu gets focus all
  3214.         # with a single keystroke.  So...focus in menus really means
  3215.         # that the object has focus *and* it is selected.  Now, this
  3216.         # assumes the selected state will be set before focus is given,
  3217.         # which appears to be the case from empirical analysis of the
  3218.         # event stream.  But of course, all menu items and menus in
  3219.         # the complete menu path will have their selected state set,
  3220.         # so, we really only care about the leaf menu or menu item
  3221.         # that it selected.]]]
  3222.         #
  3223.         role = event.source.getRole()
  3224.         if role in (pyatspi.ROLE_MENU,
  3225.                     pyatspi.ROLE_MENU_ITEM,
  3226.                     pyatspi.ROLE_CHECK_MENU_ITEM,
  3227.                     pyatspi.ROLE_RADIO_MENU_ITEM):
  3228.             try:
  3229.                 if event.source.querySelection().nSelectedChildren > 0:
  3230.                     return
  3231.             except:
  3232.                 pass
  3233.  
  3234.         # [[[TODO: WDW - HACK to deal with the fact that active cells
  3235.         # may or may not get focus.  Their parents, however, do tend to
  3236.         # get focus, but when the parent gets focus, it really means
  3237.         # that the selected child in it has focus.  Of course, this all
  3238.         # breaks when more than one child is selected.  Then, we really
  3239.         # need to depend upon the model where focus really works.]]]
  3240.         #
  3241.         newFocus = event.source
  3242.  
  3243.         if role in (pyatspi.ROLE_LAYERED_PANE,
  3244.                     pyatspi.ROLE_TABLE,
  3245.                     pyatspi.ROLE_TREE_TABLE,
  3246.                     pyatspi.ROLE_TREE):
  3247.             if event.source.childCount:
  3248.                 # We might have tucked away some information for this
  3249.                 # thing in the onActiveDescendantChanged method.
  3250.                 #
  3251.                 if "activeDescendantInfo" in self.pointOfReference:
  3252.                     [parent, index] = \
  3253.                         self.pointOfReference['activeDescendantInfo']
  3254.                     newFocus = parent[index]
  3255.  
  3256.                 else:
  3257.                     # Well...we'll first see if there is a selection.  If there
  3258.                     # is, we'll use it.
  3259.                     #
  3260.                     try:
  3261.                         selection = event.source.querySelection()
  3262.                     except NotImplementedError:
  3263.                         selection = None
  3264.                     if selection and selection.nSelectedChildren > 0:
  3265.                         newFocus = selection.getSelectedChild(0)
  3266.  
  3267.         orca.setLocusOfFocus(event, newFocus)
  3268.  
  3269.     def onNameChanged(self, event):
  3270.         """Called whenever a property on an object changes.
  3271.  
  3272.         Arguments:
  3273.         - event: the Event
  3274.         """
  3275.  
  3276.         # [[[TODO: WDW - HACK because gnome-terminal issues a name changed
  3277.         # event for the edit preferences dialog even though the name really
  3278.         # didn't change.  I'm guessing this is going to be a vagary in all
  3279.         # of GTK+.]]]
  3280.         #
  3281.         if event.source and (event.source.getRole() == pyatspi.ROLE_DIALOG) \
  3282.            and (event.source == orca_state.locusOfFocus):
  3283.             return
  3284.  
  3285.         # We do this because we can get name change events even if the
  3286.         # name doesn't change.  [[[TODO: WDW - I'm hesitant to rip the
  3287.         # above TODO out, though, because it's been in here for so long.]]]
  3288.         #
  3289.         if self.pointOfReference.get('oldName', None) == event.source.name:
  3290.             return
  3291.  
  3292.         self.pointOfReference['oldName'] = event.source.name
  3293.         orca.visualAppearanceChanged(event, event.source)
  3294.  
  3295.     def _speakContiguousSelection(self, obj, relationship):
  3296.         """Check if the contiguous object has a selection. If it does, then
  3297.         speak it. If the user pressed Shift-Down, then look for an object 
  3298.         with a RELATION_FLOWS_FROM relationship. If they pressed Shift-Up,
  3299.         then look for a RELATION_FLOWS_TO relationship.
  3300.  
  3301.         Arguments:
  3302.         - the current text object
  3303.         - the flows relationship (RELATION_FLOWS_FROM or RELATION_FLOWS_TO).
  3304.  
  3305.         Returns an indication of whether anything was spoken.
  3306.         """
  3307.  
  3308.         lastPos = self.pointOfReference.get("lastCursorPosition")
  3309.  
  3310.         # Reasons to NOT speak contiguous selections:
  3311.         #
  3312.         # 1. The new cursor position is in the same object as the old
  3313.         #    cursor position. (The change in selection is all within
  3314.         #    the current object.)
  3315.         # 2. If we are selecting up line by line from the beginning of
  3316.         #    the line and have just crossed into a new object, the change
  3317.         #    in selection is the previous line (which has just become
  3318.         #    selected).  Nothing has changed on the line we came from.
  3319.         #
  3320.         if self.isSameObject(lastPos[0], obj) \
  3321.            or relationship == pyatspi.RELATION_FLOWS_TO and lastPos[1] == 0:
  3322.             return False
  3323.  
  3324.         selSpoken = False
  3325.         current = obj
  3326.         for relation in current.getRelationSet():
  3327.             if relation.getRelationType() == relationship:
  3328.                 obj = relation.getTarget(0)
  3329.                 objText = obj.queryText()
  3330.  
  3331.                 # When selecting down across paragraph boundaries, what
  3332.                 # we've (un)selected on (what is now) the previous line
  3333.                 # is from wherever the cursor used to be to the end of 
  3334.                 # the line.
  3335.                 #
  3336.                 if relationship == pyatspi.RELATION_FLOWS_FROM:
  3337.                     start, end = lastPos[1], objText.characterCount
  3338.  
  3339.                 # When selecting up across paragraph boundaries, what
  3340.                 # we've (un)selected on (what is now) the next line is
  3341.                 # from the beginning of the line to wherever the cursor
  3342.                 # used to be.
  3343.                 #
  3344.                 else:
  3345.                     start, end = 0, lastPos[1]
  3346.  
  3347.                 if objText.getNSelections() > 0:
  3348.                     [textContents, startOffset, endOffset] = \
  3349.                         self.whereAmI.getTextSelection(obj)
  3350.  
  3351.                     # Now that we have the full selection, adjust based
  3352.                     # on the relation type. (see above comment)
  3353.                     #
  3354.                     startOffset = start or startOffset
  3355.                     endOffset = end or endOffset
  3356.                     self.sayPhrase(obj, startOffset, endOffset)
  3357.                     selSpoken = True
  3358.                 else:
  3359.                     # We don't have selections in this object. But we're
  3360.                     # here, which means that something is selected in a
  3361.                     # neighboring object and the text in this object must
  3362.                     # have just become unselected and needs to be spoken.
  3363.                     #
  3364.                     self.sayPhrase(obj, start, end)
  3365.                     selSpoken = True
  3366.  
  3367.         return selSpoken
  3368.  
  3369.     def _presentTextAtNewCaretPosition(self, event, otherObj=None):
  3370.         """Updates braille, magnification, and outputs speech for the 
  3371.         event.source or the otherObj."""
  3372.  
  3373.         obj = otherObj or event.source
  3374.         text = obj.queryText()
  3375.  
  3376.         if obj:
  3377.             mag.magnifyAccessible(event, obj)
  3378.  
  3379.         # Update the Braille display - if we can just reposition
  3380.         # the cursor, then go for it.
  3381.         #
  3382.         brailleNeedsRepainting = True
  3383.         line = braille.getShowingLine()
  3384.         for region in line.regions:
  3385.             if isinstance(region, braille.Text) \
  3386.                and (region.accessible == obj):
  3387.                 if region.repositionCursor():
  3388.                     braille.refresh(True)
  3389.                     brailleNeedsRepainting = False
  3390.                 break
  3391.  
  3392.         if brailleNeedsRepainting:
  3393.             self.updateBraille(obj)
  3394.  
  3395.         if not orca_state.lastInputEvent:
  3396.             return
  3397.  
  3398.         if isinstance(orca_state.lastInputEvent, input_event.MouseButtonEvent):
  3399.             if not orca_state.lastInputEvent.pressed:
  3400.                 self.sayLine(obj)
  3401.             return
  3402.  
  3403.         # Guess why the caret moved and say something appropriate.
  3404.         # [[[TODO: WDW - this motion assumes traditional GUI
  3405.         # navigation gestures.  In an editor such as vi, line up and
  3406.         # down is done via other actions such as "i" or "j".  We may
  3407.         # need to think about this a little harder.]]]
  3408.         #
  3409.         if not isinstance(orca_state.lastInputEvent,
  3410.                           input_event.KeyboardEvent):
  3411.             return
  3412.  
  3413.         keyString = orca_state.lastNonModifierKeyEvent.event_string
  3414.         mods = orca_state.lastInputEvent.modifiers
  3415.         isControlKey = mods & settings.CTRL_MODIFIER_MASK
  3416.         isShiftKey = mods & settings.SHIFT_MODIFIER_MASK
  3417.         lastPos = self.pointOfReference.get("lastCursorPosition")
  3418.         hasLastPos = (lastPos != None)
  3419.  
  3420.         if (keyString == "Up") or (keyString == "Down"):
  3421.             # If the user has typed Shift-Up or Shift-Down, then we want
  3422.             # to speak the text that has just been selected or unselected,
  3423.             # otherwise we speak the new line where the text cursor is
  3424.             # currently positioned.
  3425.             #
  3426.             if hasLastPos and isShiftKey and not isControlKey:
  3427.                 if keyString == "Up":
  3428.                     # If we have just crossed a paragraph boundary with
  3429.                     # Shift+Up, what we've selected in this object starts
  3430.                     # with the current offset and goes to the end of the
  3431.                     # paragraph.
  3432.                     # 
  3433.                     if not self.isSameObject(lastPos[0], obj):
  3434.                         [startOffset, endOffset] = \
  3435.                             text.caretOffset, text.characterCount
  3436.                     else:
  3437.                         [startOffset, endOffset] \
  3438.                                              = self.getOffsetsForPhrase(obj)
  3439.                     self.sayPhrase(obj, startOffset, endOffset)
  3440.                     selSpoken = self._speakContiguousSelection(obj,
  3441.                                                    pyatspi.RELATION_FLOWS_TO)
  3442.                 else:
  3443.                     selSpoken = self._speakContiguousSelection(obj,
  3444.                                                  pyatspi.RELATION_FLOWS_FROM)
  3445.  
  3446.                     # If we have just crossed a paragraph boundary with
  3447.                     # Shift+Down, what we've selected in this object starts
  3448.                     # with the beginning of the paragraph and goes to the
  3449.                     # current offset.
  3450.                     #
  3451.                     if not self.isSameObject(lastPos[0], obj):
  3452.                         [startOffset, endOffset] = 0, text.caretOffset
  3453.                     else:
  3454.                         [startOffset, endOffset] \
  3455.                                              = self.getOffsetsForPhrase(obj)
  3456.  
  3457.                     if startOffset != endOffset:
  3458.                         self.sayPhrase(obj, startOffset, endOffset)
  3459.  
  3460.             else:
  3461.                 [startOffset, endOffset] = self.getOffsetsForLine(obj)
  3462.                 self.sayLine(obj)
  3463.  
  3464.         elif (keyString == "Left") or (keyString == "Right"):
  3465.             # If the user has typed Control-Shift-Up or Control-Shift-Dowm,
  3466.             # then we want to speak the text that has just been selected
  3467.             # or unselected, otherwise if the user has typed Control-Left
  3468.             # or Control-Right, we speak the current word otherwise we speak
  3469.             # the character at the text cursor position.
  3470.             #
  3471.             inNewObj = hasLastPos and not self.isSameObject(lastPos[0], obj)
  3472.  
  3473.             if hasLastPos and not inNewObj and isShiftKey and isControlKey:
  3474.                 [startOffset, endOffset] = self.getOffsetsForPhrase(obj)
  3475.                 self.sayPhrase(obj, startOffset, endOffset)
  3476.             elif isControlKey and not inNewObj:
  3477.                 [startOffset, endOffset] = self.getOffsetsForWord(obj)
  3478.                 if startOffset == endOffset:
  3479.                     self.sayCharacter(obj)
  3480.                 else:
  3481.                     self.sayWord(obj)
  3482.             else:
  3483.                 [startOffset, endOffset] = self.getOffsetsForChar(obj)
  3484.                 self.sayCharacter(obj)
  3485.  
  3486.         elif keyString == "Page_Up":
  3487.             # If the user has typed Control-Shift-Page_Up, then we want
  3488.             # to speak the text that has just been selected or unselected,
  3489.             # otherwise if the user has typed Control-Page_Up, then we
  3490.             # speak the character to the right of the current text cursor
  3491.             # position otherwise we speak the current line.
  3492.             #
  3493.             if hasLastPos and isShiftKey and isControlKey:
  3494.                 [startOffset, endOffset] = self.getOffsetsForPhrase(obj)
  3495.                 self.sayPhrase(obj, startOffset, endOffset)
  3496.             elif isControlKey:
  3497.                 [startOffset, endOffset] = self.getOffsetsForChar(obj)
  3498.                 self.sayCharacter(obj)
  3499.             else:
  3500.                 [startOffset, endOffset] = self.getOffsetsForLine(obj)
  3501.                 self.sayLine(obj)
  3502.  
  3503.         elif keyString == "Page_Down":
  3504.             # If the user has typed Control-Shift-Page_Down, then we want
  3505.             # to speak the text that has just been selected or unselected,
  3506.             # otherwise if the user has just typed Page_Down, then we speak
  3507.             # the current line.
  3508.             #
  3509.             if hasLastPos and isShiftKey and isControlKey:
  3510.                 [startOffset, endOffset] = self.getOffsetsForPhrase(obj)
  3511.                 self.sayPhrase(obj, startOffset, endOffset)
  3512.             else:
  3513.                 [startOffset, endOffset] = self.getOffsetsForLine(obj)
  3514.                 self.sayLine(obj)
  3515.  
  3516.         elif (keyString == "Home") or (keyString == "End"):
  3517.             # If the user has typed Shift-Home or Shift-End, then we want
  3518.             # to speak the text that has just been selected or unselected,
  3519.             # otherwise if the user has typed Control-Home or Control-End,
  3520.             # then we speak the current line otherwise we speak the character
  3521.             # to the right of the current text cursor position.
  3522.             #
  3523.             if hasLastPos and isShiftKey and not isControlKey:
  3524.                 [startOffset, endOffset] = self.getOffsetsForPhrase(obj)
  3525.                 self.sayPhrase(obj, startOffset, endOffset)
  3526.             elif isControlKey:
  3527.                 [startOffset, endOffset] = self.getOffsetsForLine(obj)
  3528.                 self.sayLine(obj)
  3529.             else:
  3530.                 [startOffset, endOffset] = self.getOffsetsForChar(obj)
  3531.                 self.sayCharacter(obj)
  3532.  
  3533.         else:
  3534.             startOffset = text.caretOffset
  3535.             endOffset = text.caretOffset
  3536.  
  3537.         self._saveLastCursorPosition(obj, text.caretOffset)
  3538.         self._saveSpokenTextRange(startOffset, endOffset)
  3539.  
  3540.     def onCaretMoved(self, event):
  3541.         """Called whenever the caret moves.
  3542.  
  3543.         Arguments:
  3544.         - event: the Event
  3545.         """
  3546.  
  3547.         # Ignore caret movements from non-focused objects, unless the
  3548.         # currently focused object is the parent of the object which
  3549.         # has the caret.
  3550.         #
  3551.         if (event.source != orca_state.locusOfFocus) \
  3552.             and (event.source.parent != orca_state.locusOfFocus):
  3553.             return
  3554.  
  3555.         # We always automatically go back to focus tracking mode when
  3556.         # the caret moves in the focused object.
  3557.         #
  3558.         if self.flatReviewContext:
  3559.             self.toggleFlatReviewMode()
  3560.  
  3561.         self._presentTextAtNewCaretPosition(event)
  3562.  
  3563.     def onTextDeleted(self, event):
  3564.         """Called whenever text is deleted from an object.
  3565.  
  3566.         Arguments:
  3567.         - event: the Event
  3568.         """
  3569.  
  3570.         # Ignore text deletions from non-focused objects, unless the
  3571.         # currently focused object is the parent of the object from which
  3572.         # text was deleted
  3573.         #
  3574.         if (event.source != orca_state.locusOfFocus) \
  3575.             and (event.source.parent != orca_state.locusOfFocus):
  3576.             return
  3577.  
  3578.         # We'll also ignore sliders because we get their output via
  3579.         # their values changing.
  3580.         #
  3581.         if event.source.getRole() == pyatspi.ROLE_SLIDER:
  3582.             return
  3583.  
  3584.         # [[[NOTE: WDW - if we handle events synchronously, we'll
  3585.         # be looking at the text object *before* the text was
  3586.         # actually removed from the object.  If we handle events
  3587.         # asynchronously, we'll be looking at the text object
  3588.         # *after* the text was removed.  The importance of knowing
  3589.         # this is that the output will differ depending upon how
  3590.         # orca.settings.asyncMode has been set.  For example, the
  3591.         # regression tests run in synchronous mode, so the output
  3592.         # they see will not be the same as what the user normally
  3593.         # experiences.]]]
  3594.  
  3595.         self.updateBraille(event.source)
  3596.  
  3597.         # The any_data member of the event object has the deleted text in
  3598.         # it - If the last key pressed was a backspace or delete key,
  3599.         # speak the deleted text.  [[[TODO: WDW - again, need to think
  3600.         # about the ramifications of this when it comes to editors such
  3601.         # as vi or emacs.
  3602.         #
  3603.         if (not orca_state.lastInputEvent) \
  3604.             or \
  3605.             (not isinstance(orca_state.lastInputEvent,
  3606.                             input_event.KeyboardEvent)):
  3607.             return
  3608.  
  3609.         keyString = orca_state.lastNonModifierKeyEvent.event_string
  3610.         controlPressed = orca_state.lastInputEvent.modifiers \
  3611.                          & settings.CTRL_MODIFIER_MASK
  3612.         text = event.source.queryText()
  3613.         if keyString == "BackSpace":
  3614.             # Speak the character that has just been deleted.
  3615.             #
  3616.             character = event.any_data
  3617.  
  3618.         elif (keyString == "Delete") \
  3619.              or (keyString == "D" and controlPressed):
  3620.             # Speak the character to the right of the caret after
  3621.             # the current right character has been deleted.
  3622.             #
  3623.             offset = text.caretOffset
  3624.             [character, startOffset, endOffset] = \
  3625.                 text.getTextAtOffset(
  3626.                     offset,
  3627.                     pyatspi.TEXT_BOUNDARY_CHAR)
  3628.  
  3629.         else:
  3630.             return
  3631.  
  3632.         if self.getLinkIndex(event.source, text.caretOffset) >= 0:
  3633.             voice = self.voices[settings.HYPERLINK_VOICE]
  3634.         elif character.isupper():
  3635.             voice = self.voices[settings.UPPERCASE_VOICE]
  3636.         else:
  3637.             voice = self.voices[settings.DEFAULT_VOICE]
  3638.  
  3639.         # We won't interrupt what else might be being spoken
  3640.         # right now because it is typically something else
  3641.         # related to this event.
  3642.         #
  3643.         if len(character.decode('utf-8')) == 1:
  3644.             speech.speakCharacter(character, voice)
  3645.         else:
  3646.             speech.speak(character, voice, False)
  3647.  
  3648.     def onTextInserted(self, event):
  3649.         """Called whenever text is inserted into an object.
  3650.  
  3651.         Arguments:
  3652.         - event: the Event
  3653.         """
  3654.  
  3655.         # Ignore text insertions from non-focused objects, unless the
  3656.         # currently focused object is the parent of the object from which
  3657.         # text was inserted.
  3658.         #
  3659.         if (event.source != orca_state.locusOfFocus) \
  3660.             and (event.source.parent != orca_state.locusOfFocus):
  3661.             return
  3662.  
  3663.         # We'll also ignore sliders because we get their output via
  3664.         # their values changing.
  3665.         #
  3666.         if event.source.getRole() == pyatspi.ROLE_SLIDER:
  3667.             return
  3668.  
  3669.         self.updateBraille(event.source)
  3670.  
  3671.         text = event.any_data
  3672.  
  3673.         # If this is a spin button, then speak the text and return.
  3674.         #
  3675.         if event.source.getRole() == pyatspi.ROLE_SPIN_BUTTON:
  3676.             # We are using getTextLineAtCaret here instead of the "text"
  3677.             # variable, because of a problem with selected text in spin
  3678.             # buttons. See bug #520395 for more details.
  3679.             #
  3680.             [spinValue, caretOffset, startOffset] = \
  3681.                 self.getTextLineAtCaret(event.source)
  3682.             speech.speak(spinValue)
  3683.             return
  3684.  
  3685.         # If the last input event was a keyboard event, check to see if
  3686.         # the text for this event matches what the user typed. If it does,
  3687.         # then don't speak it.
  3688.         #
  3689.         # Note that the text widgets sometimes compress their events,
  3690.         # thus we might get a longer string from a single text inserted
  3691.         # event, while we also get individual keyboard events for the
  3692.         # characters used to type the string.  This is ugly.  We attempt
  3693.         # to handle it here by only echoing text if we think it was the
  3694.         # result of a command (e.g., a paste operation).
  3695.         #
  3696.         # Note that we have to special case the space character as it
  3697.         # comes across as "space" in the keyboard event and " " in the
  3698.         # text event.
  3699.         #
  3700.         speakThis = False
  3701.         if isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent):
  3702.             keyString = orca_state.lastNonModifierKeyEvent.event_string
  3703.             wasAutoComplete = (event.source.getRole() == pyatspi.ROLE_TEXT and \
  3704.                                event.source.queryText().getNSelections())
  3705.             wasCommand = orca_state.lastInputEvent.modifiers \
  3706.                          & settings.COMMAND_MODIFIER_MASK
  3707.             if (text == " " and keyString == "space") \
  3708.                 or (text == keyString):
  3709.                 pass
  3710.             elif wasCommand or wasAutoComplete:
  3711.                 speakThis = True
  3712.             elif (event.source.getRole() == pyatspi.ROLE_PASSWORD_TEXT) and \
  3713.                  settings.enableKeyEcho and settings.enablePrintableKeys:
  3714.                 # Echoing "star" is preferable to echoing the descriptive
  3715.                 # name of the bullet that has appeared (e.g. "black circle")
  3716.                 #
  3717.                 text = "*"
  3718.                 speakThis = True
  3719.  
  3720.         elif isinstance(orca_state.lastInputEvent, \
  3721.                         input_event.MouseButtonEvent) and \
  3722.              orca_state.lastInputEvent.button == "2":
  3723.             speakThis = True
  3724.  
  3725.         if speakThis:
  3726.             if text.isupper():
  3727.                 speech.speak(text, self.voices[settings.UPPERCASE_VOICE])
  3728.             else:
  3729.                 speech.speak(text)
  3730.  
  3731.         try:
  3732.             text = event.source.queryText()
  3733.         except NotImplementedError:
  3734.             return
  3735.  
  3736.         # Pylint is confused and flags this and similar lines, with the 
  3737.         # following error:
  3738.         #
  3739.         # E1103:3673:Script.onTextInserted: Instance of 'str' has no 
  3740.         #'caretOffset' member (but some types could not be inferred)
  3741.         #
  3742.         # But it does, so we'll just tell pylint that we know what we
  3743.         # are doing.
  3744.         #
  3745.         # pylint: disable-msg=E1103
  3746.  
  3747.         offset = min(event.detail1, text.caretOffset - 1)
  3748.         previousOffset = offset - 1
  3749.         if (offset < 0 or previousOffset < 0):
  3750.             return
  3751.  
  3752.         [currentChar, startOffset, endOffset] = \
  3753.             text.getTextAtOffset(offset, pyatspi.TEXT_BOUNDARY_CHAR)
  3754.         [previousChar, startOffset, endOffset] = \
  3755.             text.getTextAtOffset(previousOffset, pyatspi.TEXT_BOUNDARY_CHAR)
  3756.  
  3757.         if settings.enableEchoBySentence and \
  3758.            self.isSentenceDelimiter(currentChar, previousChar):
  3759.             self.echoPreviousSentence(event.source)
  3760.  
  3761.         elif settings.enableEchoByWord and self.isWordDelimiter(currentChar):
  3762.             self.echoPreviousWord(event.source)
  3763.  
  3764.     def onActiveDescendantChanged(self, event):
  3765.         """Called when an object who manages its own descendants detects a
  3766.         change in one of its children.
  3767.  
  3768.         Arguments:
  3769.         - event: the Event
  3770.         """
  3771.  
  3772.         if not event.source.getState().contains(pyatspi.STATE_FOCUSED):
  3773.             return
  3774.  
  3775.         # There can be cases when the object that fires an
  3776.         # active-descendant-changed event has no children. In this case,
  3777.         # use the object that fired the event, otherwise, use the child.
  3778.         #
  3779.         child = event.any_data
  3780.         if child:
  3781.             speech.stop()
  3782.             orca.setLocusOfFocus(event, child)
  3783.         else:
  3784.             orca.setLocusOfFocus(event, event.source)
  3785.  
  3786.         # We'll tuck away the activeDescendant information for future
  3787.         # reference since the AT-SPI gives us little help in finding
  3788.         # this.
  3789.         #
  3790.         if orca_state.locusOfFocus \
  3791.            and (orca_state.locusOfFocus != event.source):
  3792.             self.pointOfReference['activeDescendantInfo'] = \
  3793.                 [orca_state.locusOfFocus.parent,
  3794.                  orca_state.locusOfFocus.getIndexInParent()]
  3795.  
  3796.     def onLinkSelected(self, event):
  3797.         """Called when a hyperlink is selected in a text area.
  3798.  
  3799.         Arguments:
  3800.         - event: the Event
  3801.         """
  3802.  
  3803.         # [[[TODO: WDW - HACK one might think we could expect an
  3804.         # application to keep its name, but it appears as though
  3805.         # yelp has an identity problem and likes to start calling
  3806.         # itself "yelp," but then changes its name to "Mozilla"
  3807.         # on Fedora Core 4 after the user selects a link.  So, we'll
  3808.         # just assume that link-selected events always come from the
  3809.         # application with focus.]]]
  3810.         #
  3811.         #if orca_state.locusOfFocus \
  3812.         #   and (orca_state.locusOfFocus.app == event.source.app):
  3813.         #    orca.setLocusOfFocus(event, event.source)
  3814.         orca.setLocusOfFocus(event, event.source)
  3815.  
  3816.     def onStateChanged(self, event):
  3817.         """Called whenever an object's state changes.
  3818.  
  3819.         Arguments:
  3820.         - event: the Event
  3821.         """
  3822.  
  3823.         # Do we care?
  3824.         #
  3825.         if event.type.startswith("object:state-changed:active"):
  3826.             if self.findCommandRun:
  3827.                 self.findCommandRun = False
  3828.                 self.find()
  3829.                 return
  3830.  
  3831.         if event.type.startswith("object:state-changed:selected") \
  3832.            and orca_state.locusOfFocus:
  3833.             # If this selection state change is for the object which
  3834.             # currently has the locus of focus, and the last keyboard
  3835.             # event was Space, or we are a focused table cell and we
  3836.             # arrowed Down or Up and are now selected, then let the
  3837.             # user know the selection state.
  3838.             # See bugs #486908 and #519564 for more details.
  3839.             #
  3840.             if isinstance(orca_state.lastInputEvent,
  3841.                           input_event.KeyboardEvent):
  3842.                 if orca_state.lastNonModifierKeyEvent:
  3843.                     keyString = orca_state.lastNonModifierKeyEvent.event_string
  3844.                 else:
  3845.                     keyString = None
  3846.                 mods = orca_state.lastInputEvent.modifiers
  3847.                 isControlKey = mods & settings.CTRL_MODIFIER_MASK
  3848.                 state = orca_state.locusOfFocus.getState()
  3849.                 announceState = False
  3850.  
  3851.                 if state.contains(pyatspi.STATE_FOCUSED) and \
  3852.                    self.isSameObject(event.source, orca_state.locusOfFocus):
  3853.  
  3854.                     if keyString == "space":
  3855.                         if isControlKey:
  3856.                             announceState = True
  3857.                         else:
  3858.                             # Weed out a bogus situation. If we are already
  3859.                             # selected and the user presses "space" again,
  3860.                             # we don't want to speak the intermediate
  3861.                             # "unselected" state.
  3862.                             #
  3863.                             eventState = event.source.getState()
  3864.                             selected = eventState.contains(\
  3865.                                            pyatspi.STATE_SELECTED)
  3866.                             announceState = (selected and event.detail1)
  3867.  
  3868.                     if (keyString == "Down" or keyString == "Up") \
  3869.                        and event.source.getRole() == pyatspi.ROLE_TABLE_CELL \
  3870.                        and state.contains(pyatspi.STATE_SELECTED):
  3871.                         announceState = True
  3872.  
  3873.                 if announceState:
  3874.                     if event.detail1:
  3875.                         # Translators: this object is now selected.
  3876.                         # Let the user know this.
  3877.                         #
  3878.                         #
  3879.                         speech.speak(C_("text", "selected"), None, False)
  3880.                     else:
  3881.                         # Translators: this object is now unselected.
  3882.                         # Let the user know this.
  3883.                         #
  3884.                         #
  3885.                         speech.speak(C_("text", "unselected"), None, False)
  3886.                     return
  3887.  
  3888.         if event.type.startswith("object:state-changed:focused"):
  3889.             iconified = False
  3890.             try:
  3891.                 window = self.getTopLevel(event.source)
  3892.                 iconified = window.getState().contains(pyatspi.STATE_ICONIFIED)
  3893.             except:
  3894.                 debug.println(debug.LEVEL_FINEST,
  3895.                         "onStateChanged: could not get frame of focused item")
  3896.             if not iconified:
  3897.                 if event.detail1:
  3898.                     self.onFocus(event)
  3899.                 # We don't set locus of focus of None here because it
  3900.                 # wreaks havoc on the code that determines the context
  3901.                 # when you tab from widget to widget.  For example,
  3902.                 # tabbing between panels in the gtk-demo buttons demo.
  3903.                 #
  3904.                 #else:
  3905.                 #    orca.setLocusOfFocus(event, None)
  3906.                 return
  3907.  
  3908.         # Handle tooltip popups.
  3909.         #
  3910.         if event.source.getRole() == pyatspi.ROLE_TOOL_TIP:
  3911.             obj = event.source
  3912.  
  3913.             if event.type.startswith("object:state-changed:showing"):
  3914.                 if event.detail1 == 1:
  3915.                     self.presentTooltip(obj)
  3916.                 elif orca_state.locusOfFocus \
  3917.                     and isinstance(orca_state.lastInputEvent,
  3918.                                    input_event.KeyboardEvent) \
  3919.                     and (orca_state.lastNonModifierKeyEvent.event_string \
  3920.                                                                   == "F1"):
  3921.                     self.updateBraille(orca_state.locusOfFocus)
  3922.                     utterances = self.speechGenerator.getSpeech(\
  3923.                         orca_state.locusOfFocus,
  3924.                         False)
  3925.                     utterances.extend(self.tutorialGenerator.getTutorial(
  3926.                                       orca_state.locusOfFocus, False))
  3927.                     speech.speakUtterances(utterances)
  3928.             return
  3929.  
  3930.         if event.source.getRole() in state_change_notifiers:
  3931.             notifiers = state_change_notifiers[event.source.getRole()]
  3932.             found = False
  3933.             for state in notifiers:
  3934.                 if state and event.type.endswith(state):
  3935.                     found = True
  3936.                     break
  3937.             if found:
  3938.                 orca.visualAppearanceChanged(event, event.source)
  3939.  
  3940.         # [[[TODO: WDW - HACK we'll handle this in the visual appearance
  3941.         # changed handler.]]]
  3942.         #
  3943.         # The object with focus might become insensitive, so we need to
  3944.         # flag that.  This typically occurs in wizard dialogs such as
  3945.         # the account setup assistant in Evolution.
  3946.         #
  3947.         #if event.type.endswith("sensitive") \
  3948.         #   and (event.detail1 == 0) \
  3949.         #   and event.source == orca_state.locusOfFocus:
  3950.         #    print "FOO INSENSITIVE"
  3951.         #    #orca.setLocusOfFocus(event, None)
  3952.  
  3953.     def getOffsetsForPhrase(self, obj):
  3954.         """Return the start and end offset for the given phrase
  3955.  
  3956.         Arguments:
  3957.         - obj: the Accessible object
  3958.         """
  3959.  
  3960.         text = obj.queryText()
  3961.         lastPos = self.pointOfReference.get("lastCursorPosition")
  3962.         startOffset = lastPos[1]
  3963.         endOffset = text.caretOffset
  3964.  
  3965.         # Swap values if in wrong order (StarOffice is fussy about that).
  3966.         #
  3967.         if ((startOffset > endOffset) and (endOffset != -1)) or \
  3968.            (startOffset == -1):
  3969.             temp = endOffset
  3970.             endOffset = startOffset
  3971.             startOffset = temp
  3972.         return [startOffset, endOffset]
  3973.  
  3974.     def getOffsetsForLine(self, obj):
  3975.         """Return the start and end offset for the given line
  3976.  
  3977.         Arguments:
  3978.         - obj: the Accessible object
  3979.         """
  3980.  
  3981.         [line, endOffset, startOffset] = self.getTextLineAtCaret(obj)
  3982.         return [startOffset, endOffset]
  3983.  
  3984.     def getOffsetsForWord(self, obj):
  3985.         """Return the start and end offset for the given word
  3986.  
  3987.         Arguments:
  3988.         - obj: the Accessible object
  3989.         """
  3990.  
  3991.         text = obj.queryText()
  3992.         offset = text.caretOffset
  3993.         [word, startOffset, endOffset] = text.getTextAtOffset(offset,
  3994.                                            pyatspi.TEXT_BOUNDARY_WORD_START)
  3995.         return [startOffset, endOffset]
  3996.  
  3997.     def getOffsetsForChar(self, obj):
  3998.         """Return the start and end offset for the given character
  3999.  
  4000.         Arguments:
  4001.         - obj: the Accessible object
  4002.         """
  4003.  
  4004.         text = obj.queryText()
  4005.         offset = text.caretOffset
  4006.  
  4007.         mods = orca_state.lastInputEvent.modifiers
  4008.         if (mods & settings.SHIFT_MODIFIER_MASK) \
  4009.             and orca_state.lastNonModifierKeyEvent.event_string == "Right":
  4010.             startOffset = offset-1
  4011.             endOffset = offset
  4012.         else:
  4013.             startOffset = offset
  4014.             endOffset = offset+1
  4015.  
  4016.         return [startOffset, endOffset]
  4017.  
  4018.     def onTextSelectionChanged(self, event):
  4019.         """Called when an object's text selection changes.
  4020.  
  4021.         Arguments:
  4022.         - event: the Event
  4023.         """
  4024.  
  4025.         obj = event.source
  4026.         spokenRange = self.pointOfReference.get("spokenTextRange") or [0, 0]
  4027.         startOffset, endOffset = spokenRange
  4028.  
  4029.         if not obj.getState().contains(pyatspi.STATE_FOCUSED):
  4030.             # We're selecting across paragraph (or other text object)
  4031.             # boundaries. If we're here, the selection has changed in
  4032.             # an object which does not have the caret. We need to try
  4033.             # to sort this out.
  4034.             #
  4035.             lastPos = self.pointOfReference.get("lastCursorPosition")
  4036.             if not lastPos:
  4037.                 # We have no point of reference. Bail.
  4038.                 #
  4039.                 return
  4040.             elif endOffset - startOffset > 1:
  4041.                 # We're coming at the line from below. And didn't just
  4042.                 # land on a blank/empty line. We have other methods for
  4043.                 # dealing with this situation.
  4044.                 #
  4045.                 return
  4046.  
  4047.             # We must be approaching from the top, left, or right. Or
  4048.             # from below but we've found a blank line. Our stored point
  4049.             # of reference tells us our caret location. Figure out how
  4050.             # we got here by looking at our position with respect to
  4051.             # the event under consideration.
  4052.             #
  4053.             relationType = None
  4054.             for relation in lastPos[0].getRelationSet():
  4055.                 if relation.getRelationType() in [pyatspi.RELATION_FLOWS_FROM,
  4056.                                                   pyatspi.RELATION_FLOWS_TO] \
  4057.                    and self.isSameObject(obj, relation.getTarget(0)):
  4058.                     relationType = relation.getRelationType()
  4059.                     break
  4060.  
  4061.             # If there's a completely blank line in between our previous
  4062.             # and current locations, where we came from will lack any
  4063.             # offically-selectable characters. As a result, we won't 
  4064.             # indicate when a blank line has been selected. Under these
  4065.             # conditions, we'll try to backtrack further.
  4066.             #
  4067.             endOffset = 0
  4068.             while obj and not endOffset:
  4069.                 try:
  4070.                     endOffset = obj.queryText().characterCount
  4071.                     startOffset = max(0, endOffset - 1)
  4072.                 except:
  4073.                     pass
  4074.  
  4075.                 if not endOffset:
  4076.                     for relation in obj.getRelationSet():
  4077.                         if relation.getRelationType() == relationType:
  4078.                             obj = relation.getTarget(0)
  4079.                             break
  4080.                     else:
  4081.                         break
  4082.  
  4083.         self.speakTextSelectionState(obj, startOffset, endOffset)    
  4084.             
  4085.     def onSelectionChanged(self, event):
  4086.         """Called when an object's selection changes.
  4087.  
  4088.         Arguments:
  4089.         - event: the Event
  4090.         """
  4091.  
  4092.         if not event or not event.source:
  4093.             return
  4094.  
  4095.         # Save the event source, if it is a menu or combo box. It will be
  4096.         # useful for optimizing getComponentAtDesktopCoords in the case
  4097.         # that the  pointer is hovering over a menu item. The alternative is
  4098.         # to traverse the application's tree looking for potential moused-over
  4099.         # menu items.
  4100.         if event.source.getRole() in (pyatspi.ROLE_COMBO_BOX,
  4101.                                            pyatspi.ROLE_MENU):
  4102.             self.lastSelectedMenu = event.source
  4103.  
  4104.         # Avoid doing this with objects that manage their descendants
  4105.         # because they'll issue a descendant changed event.
  4106.         #
  4107.         if event.source.getState().contains(pyatspi.STATE_MANAGES_DESCENDANTS):
  4108.             return
  4109.  
  4110.         if event.source.getRole() == pyatspi.ROLE_COMBO_BOX:
  4111.             orca.visualAppearanceChanged(event, event.source)
  4112.  
  4113.         # We treat selected children as the locus of focus. When the
  4114.         # selection changed we want to update the locus of focus. If
  4115.         # there is no selection, we default the locus of focus to the
  4116.         # containing object.
  4117.         #
  4118.         elif (event.source != orca_state.locusOfFocus) and \
  4119.             event.source.getState().contains(pyatspi.STATE_FOCUSED):
  4120.             newFocus = event.source
  4121.             if event.source.childCount:
  4122.                 selection = event.source.querySelection()
  4123.                 if selection.nSelectedChildren > 0:
  4124.                     newFocus = selection.getSelectedChild(0)
  4125.  
  4126.             orca.setLocusOfFocus(event, newFocus)
  4127.  
  4128.     def onValueChanged(self, event):
  4129.         """Called whenever an object's value changes.  Currently, the
  4130.         value changes for non-focused objects are ignored.
  4131.  
  4132.         Arguments:
  4133.         - event: the Event
  4134.         """
  4135.  
  4136.         # We'll let caret moved and text inserted events be used to
  4137.         # manage spin buttons, since they basically are text areas.
  4138.         #
  4139.         if event.source.getRole() == pyatspi.ROLE_SPIN_BUTTON:
  4140.             return
  4141.  
  4142.         # We'll also try to ignore those objects that keep telling
  4143.         # us their value changed even though it hasn't.
  4144.         #
  4145.         value = event.source.queryValue()
  4146.         if "oldValue" in self.pointOfReference \
  4147.            and (value.currentValue == self.pointOfReference["oldValue"]):
  4148.             return
  4149.  
  4150.         orca.visualAppearanceChanged(event, event.source)
  4151.         if event.source.getState().contains(pyatspi.STATE_FOCUSED):
  4152.             self.pointOfReference["oldValue"] = value.currentValue
  4153.  
  4154.     def onWindowActivated(self, event):
  4155.         """Called whenever a toplevel window is activated.
  4156.  
  4157.         Arguments:
  4158.         - event: the Event
  4159.         """
  4160.  
  4161.         self.windowActivateTime = time.time()
  4162.         orca.setLocusOfFocus(event, event.source)
  4163.  
  4164.         # We keep track of the active window to handle situations where
  4165.         # we get window activated and window deactivated events out of
  4166.         # order (see onWindowDeactivated).
  4167.         #
  4168.         # For example, events can be:
  4169.         #
  4170.         #    window:activate   (w1)
  4171.         #    window:activate   (w2)
  4172.         #    window:deactivate (w1)
  4173.         #
  4174.         # as well as:
  4175.         #
  4176.         #    window:activate   (w1)
  4177.         #    window:deactivate (w1)
  4178.         #    window:activate   (w2)
  4179.         #
  4180.         orca_state.activeWindow = event.source
  4181.  
  4182.     def onWindowDeactivated(self, event):
  4183.         """Called whenever a toplevel window is deactivated.
  4184.  
  4185.         Arguments:
  4186.         - event: the Event
  4187.         """
  4188.  
  4189.         # If we receive a "window:deactivate" event for the object that
  4190.         # currently has focus, then stop the current speech output.
  4191.         # This is very useful for terminating long speech output from
  4192.         # commands running in gnome-terminal.
  4193.         #
  4194.         if orca_state.locusOfFocus and \
  4195.           (orca_state.locusOfFocus.getApplication() == \
  4196.              event.source.getApplication()):
  4197.             speech.stop()
  4198.  
  4199.             # Clear the braille display just in case we are about to give
  4200.             # focus to an inaccessible application. See bug #519901 for
  4201.             # more details.
  4202.             #
  4203.             braille.clear()
  4204.  
  4205.             # Hide the flat review window and reset it so that it will be
  4206.             # recreated.
  4207.             #
  4208.             if self.flatReviewContext:
  4209.                 self.drawOutline(-1, 0, 0, 0)
  4210.                 self.flatReviewContext = None
  4211.                 self.updateBraille(orca_state.locusOfFocus)
  4212.  
  4213.         # Because window activated and deactivated events may be
  4214.         # received in any order when switching from one application to
  4215.         # another, locusOfFocus and activeWindow, we really only change
  4216.         # the locusOfFocus and activeWindow when we are dealing with
  4217.         # an event from the current activeWindow.
  4218.         #
  4219.         if event.source == orca_state.activeWindow:
  4220.             orca.setLocusOfFocus(event, None)
  4221.             orca_state.activeWindow = None
  4222.  
  4223.     def onMouseButton(self, event):
  4224.         """Called whenever the user presses or releases a mouse button.
  4225.  
  4226.         Arguments:
  4227.         - event: the Event
  4228.         """
  4229.  
  4230.         # If we've received a mouse button released event, then check if
  4231.         # there are and text selections for the locus of focus and speak
  4232.         # them.
  4233.         #
  4234.         state = event.type[-1]
  4235.         if state == "r":
  4236.             obj = orca_state.locusOfFocus
  4237.             try:
  4238.                 text = obj.queryText()
  4239.             except:
  4240.                 pass
  4241.             else:
  4242.                 [textContents, startOffset, endOffset] = \
  4243.                     self.whereAmI.getTextSelections(obj, True)
  4244.                 if textContents:
  4245.                     utterances = []
  4246.                     utterances.append(textContents)
  4247.  
  4248.                     # Translators: when the user selects (highlights) text in
  4249.                     # a document, Orca lets them know this.
  4250.                     #
  4251.                     utterances.append(C_("text", "selected"))
  4252.                     speech.speakUtterances(utterances)
  4253.                 self.updateBraille(orca_state.locusOfFocus)
  4254.  
  4255.     def noOp(self, event):
  4256.         """Just here to capture events.
  4257.  
  4258.         Arguments:
  4259.         - event: the Event
  4260.         """
  4261.         pass
  4262.  
  4263.     ########################################################################
  4264.     #                                                                      #
  4265.     # Utilities                                                            #
  4266.     #                                                                      #
  4267.     ########################################################################
  4268.  
  4269.     def isLayoutOnly(self, obj):
  4270.         """Returns True if the given object is a table and is for layout
  4271.         purposes only."""
  4272.  
  4273.         layoutOnly = False
  4274.  
  4275.         if obj:
  4276.             attributes = obj.getAttributes()
  4277.         else:
  4278.             attributes = None
  4279.  
  4280.         if obj and (obj.getRole() == pyatspi.ROLE_TABLE) and attributes:
  4281.             for attribute in attributes:
  4282.                 if attribute == "layout-guess:true":
  4283.                     layoutOnly = True
  4284.                     break
  4285.         elif obj and (obj.getRole() == pyatspi.ROLE_PANEL):
  4286.             text = self.getDisplayedText(obj)
  4287.             label = self.getDisplayedLabel(obj)
  4288.             if not ((label and len(label)) or (text and len(text))):
  4289.                 layoutOnly = True
  4290.  
  4291.         if layoutOnly:
  4292.             debug.println(debug.LEVEL_FINEST,
  4293.                           "Object deemed to be for layout purposes only: %s" \
  4294.                           % obj)
  4295.  
  4296.         return layoutOnly
  4297.  
  4298.     def toggleTableCellReadMode(self, inputEvent=None):
  4299.         """Toggles an indicator for whether we should just read the current
  4300.         table cell or read the whole row."""
  4301.  
  4302.         settings.readTableCellRow = not settings.readTableCellRow
  4303.         if settings.readTableCellRow:
  4304.             # Translators: when users are navigating a table, they
  4305.             # sometimes want the entire row of a table read, or
  4306.             # they just want the current cell to be presented to them.
  4307.             #
  4308.             line = _("Speak row")
  4309.         else:
  4310.             # Translators: when users are navigating a table, they
  4311.             # sometimes want the entire row of a table read, or
  4312.             # they just want the current cell to be presented to them.
  4313.             #
  4314.             line = _("Speak cell")
  4315.  
  4316.         speech.speak(line)
  4317.  
  4318.         return True
  4319.  
  4320.     def getAtkNameForAttribute(self, attribName):
  4321.         """Converts the given attribute name into the Atk equivalent. This
  4322.         is necessary because an application or toolkit (e.g. Gecko) might
  4323.         invent entirely new names for the same attributes.
  4324.  
  4325.         Arguments:
  4326.         - attribName: The name of the text attribute
  4327.  
  4328.         Returns the Atk equivalent name if found or attribName otherwise.
  4329.         """
  4330.  
  4331.         return self.attributeNamesDict.get(attribName, attribName)
  4332.  
  4333.     def getAppNameForAttribute(self, attribName):
  4334.         """Converts the given Atk attribute name into the application's
  4335.         equivalent. This is necessary because an application or toolkit
  4336.         (e.g. Gecko) might invent entirely new names for the same text
  4337.         attributes.
  4338.  
  4339.         Arguments:
  4340.         - attribName: The name of the text attribute
  4341.  
  4342.         Returns the application's equivalent name if found or attribName
  4343.         otherwise.
  4344.         """
  4345.  
  4346.         for key, value in self.attributeNamesDict.items():
  4347.             if value == attribName:
  4348.                 return key
  4349.  
  4350.         return attribName
  4351.  
  4352.     def textAttrsToDictionary(self, tokenString):
  4353.         """Converts a string of text attribute tokens of the form
  4354.         <key>:<value>; into a dictionary of keys and values.
  4355.         Text before the colon is the key and text afterwards is the
  4356.         value. If there is a final semi-colon, then it's ignored.
  4357.  
  4358.         Arguments:
  4359.         - tokenString: the string of tokens containing <key>:<value>; pairs.
  4360.  
  4361.         Returns a list containing two items:
  4362.         A list of the keys in the order they were extracted from the
  4363.         text attribute string and a dictionary of key/value items.
  4364.         """
  4365.  
  4366.         keyList = []
  4367.         dictionary = {}
  4368.         allTokens = tokenString.split(";")
  4369.         for token in allTokens:
  4370.             item = token.split(":")
  4371.             if len(item) == 2:
  4372.                 key = item[0].strip()
  4373.                 attribute = item[1].strip()
  4374.                 keyList.append(key)
  4375.                 dictionary[key] = attribute
  4376.  
  4377.         return [keyList, dictionary]
  4378.  
  4379.     def outputCharAttributes(self, keys, attributes):
  4380.         """Speak each of the text attributes given dictionary.
  4381.  
  4382.         Arguments:
  4383.         - attributes: a dictionary of text attributes to speak.
  4384.         """
  4385.  
  4386.         for key in keys:
  4387.             localizedKey = text_attribute_names.getTextAttributeName(key)
  4388.             if key in attributes:
  4389.                 line = ""
  4390.                 attribute = attributes[key]
  4391.                 localizedValue = \
  4392.                     text_attribute_names.getTextAttributeName(attribute)
  4393.                 if attribute:
  4394.                     key = self.getAtkNameForAttribute(key)
  4395.                     # If it's the 'weight' attribute and greater than 400, just
  4396.                     # speak it as bold, otherwise speak the weight.
  4397.                     #
  4398.                     if key == "weight" \
  4399.                        and (attribute == "bold" or int(attribute) > 400):
  4400.                         # Translators: bold as in the font sense.
  4401.                         #
  4402.                         line = _("bold")
  4403.                     elif key in ["left-margin", "right-margin"]:
  4404.                         # We need to test if we are getting a margin value
  4405.                         # that includes unit information (OOo now provides
  4406.                         # this). If not, then we will assume it's pixels.
  4407.                         #
  4408.                         numericPoint = locale.localeconv()["decimal_point"]
  4409.                         lastChar = attribute[len(attribute) - 1]
  4410.                         if lastChar == numericPoint or \
  4411.                            lastChar in self.digits:
  4412.                             # Translators: these represent the number of pixels
  4413.                             # for the left or right margins in a document.  We
  4414.                             # are hesitant to interpret the values -- they are
  4415.                             # given to us in some unknown form by the
  4416.                             # application, so we leave things in plural form
  4417.                             # here.
  4418.                             #
  4419.                             line = ngettext("%s %s pixel",
  4420.                                             "%s %s pixels",
  4421.                                             int(attribute)) % \
  4422.                                                 (localizedKey, localizedValue)
  4423.                     elif key in ["indent", "size"]:
  4424.                         # In Gecko, we seem to get these values as a number
  4425.                         # immediately followed by "px". But we'll hedge our
  4426.                         # bet.
  4427.                         #
  4428.                         value = attribute.split("px")
  4429.                         if len(value) > 1:
  4430.                             line = ngettext("%s %s pixel",
  4431.                                             "%s %s pixels",
  4432.                                             float(value[0])) % \
  4433.                                             (localizedKey, value[0])
  4434.                     elif key == "family-name":
  4435.                         # In Gecko, we get a huge list and we just want the
  4436.                         # first one.  See:
  4437.                         # http://www.w3.org/TR/CSS2/fonts.html#font-family-prop
  4438.                         #
  4439.                         localizedValue = \
  4440.                             attribute.split(",")[0].strip().strip('"')
  4441.  
  4442.                     line = line or (localizedKey + " " + localizedValue)
  4443.                     speech.speak(line)
  4444.  
  4445.     def readCharAttributes(self, inputEvent=None):
  4446.         """Reads the attributes associated with the current text character.
  4447.         Calls outCharAttributes to speak a list of attributes. By default,
  4448.         a certain set of attributes will be spoken. If this is not desired,
  4449.         then individual application scripts should override this method to
  4450.         only speak the subset required.
  4451.         """
  4452.  
  4453.         try:
  4454.             text = orca_state.locusOfFocus.queryText()
  4455.         except:
  4456.             pass
  4457.         else:
  4458.             caretOffset = text.caretOffset
  4459.  
  4460.             # Creates dictionaries of the default attributes, plus the set
  4461.             # of attributes specific to the character at the caret offset.
  4462.             # Combine these two dictionaries and then extract just the
  4463.             # entries we are interested in.
  4464.             #
  4465.             defAttributes = text.getDefaultAttributes()
  4466.             debug.println(debug.LEVEL_FINEST, \
  4467.                 "readCharAttributes: default text attributes: %s" % \
  4468.                 defAttributes)
  4469.             [defUser, defDict] = self.textAttrsToDictionary(defAttributes)
  4470.             allAttributes = defDict
  4471.  
  4472.             charAttributes = text.getAttributes(caretOffset)
  4473.             if charAttributes[0]:
  4474.                 [charList, charDict] = \
  4475.                     self.textAttrsToDictionary(charAttributes[0])
  4476.  
  4477.                 # It looks like some applications like Evolution and Star
  4478.                 # Office don't implement getDefaultAttributes(). In that
  4479.                 # case, the best we can do is use the specific text
  4480.                 # attributes for this character returned by getAttributes().
  4481.                 #
  4482.                 if allAttributes:
  4483.                     for key in charDict.keys():
  4484.                         allAttributes[key] = charDict[key]
  4485.                 else:
  4486.                     allAttributes = charDict
  4487.  
  4488.             # Get a dictionary of text attributes that the user cares about.
  4489.             #
  4490.             [userAttrList, userAttrDict] = \
  4491.                 self.textAttrsToDictionary(settings.enabledSpokenTextAttributes)
  4492.  
  4493.             # Create a dictionary of just the items we are interested in.
  4494.             # Always include size and family-name. For the others, if the
  4495.             # value is the default, then ignore it.
  4496.             #
  4497.             attributes = {}
  4498.             for key in userAttrList:
  4499.                 if key in allAttributes:
  4500.                     textAttr = allAttributes.get(key)
  4501.                     userAttr = userAttrDict.get(key)
  4502.                     if textAttr != userAttr or len(userAttr) == 0:
  4503.                         attributes[key] = textAttr
  4504.  
  4505.             self.outputCharAttributes(userAttrList, attributes)
  4506.  
  4507.             # If this is a hypertext link, then let the user know:
  4508.             #
  4509.             if self.getLinkIndex(orca_state.locusOfFocus, caretOffset) >= 0:
  4510.                 # Translators: this indicates that this piece of
  4511.                 # text is a hypertext link.
  4512.                 #
  4513.                 speech.speak(_("link"))
  4514.  
  4515.         return True
  4516.  
  4517.     def reportScriptInfo(self, inputEvent=None):
  4518.         """Output useful information on the current script via speech
  4519.         and braille.  This information will be helpful to script writers.
  4520.         """
  4521.  
  4522.         infoString = "SCRIPT INFO: Script name='%s'" % self.name
  4523.         app = orca_state.locusOfFocus.getApplication()
  4524.         if orca_state.locusOfFocus and app:
  4525.             infoString += " Application name='%s'" \
  4526.                           % app.name
  4527.  
  4528.             try:
  4529.                 infoString += " Toolkit name='%s'" \
  4530.                               % app.toolkitName
  4531.             except:
  4532.                 infoString += " Toolkit unknown"
  4533.  
  4534.             try:
  4535.                 infoString += " Version='%s'" \
  4536.                               % app.version
  4537.             except:
  4538.                 infoString += " Version unknown"
  4539.  
  4540.             debug.println(debug.LEVEL_INFO, infoString)
  4541.             speech.speak(infoString)
  4542.             braille.displayMessage(infoString)
  4543.  
  4544.         return True
  4545.  
  4546.     def bypassNextCommand(self, inputEvent=None):
  4547.         """Causes the next keyboard command to be ignored by Orca
  4548.         and passed along to the current application.
  4549.  
  4550.         Returns True to indicate the input event has been consumed.
  4551.         """
  4552.  
  4553.         # Translators: Orca normally intercepts all keyboard
  4554.         # commands and only passes them along to the current
  4555.         # application when they are not Orca commands.  This
  4556.         # command causes the next command issued to be passed
  4557.         # along to the current application, bypassing Orca's
  4558.         # interception of it.
  4559.         #
  4560.         speech.speak(_("Bypass mode enabled."))
  4561.         orca_state.bypassNextCommand = True
  4562.         return True
  4563.  
  4564.     def enterLearnMode(self, inputEvent=None):
  4565.         """Turns learn mode on.  The user must press the escape key to exit
  4566.         learn mode.
  4567.  
  4568.         Returns True to indicate the input event has been consumed.
  4569.         """
  4570.  
  4571.         if settings.learnModeEnabled:
  4572.             return True
  4573.  
  4574.         speech.speak(
  4575.             # Translators: Orca has a "Learn Mode" that will allow
  4576.             # the user to type any key on the keyboard and hear what
  4577.             # the effects of that key would be.  The effects might
  4578.             # be what Orca would do if it had a handler for the
  4579.             # particular key combination, or they might just be to
  4580.             # echo the name of the key if Orca doesn't have a handler.
  4581.             # This text here is what is spoken to the user.
  4582.             #
  4583.             _("Entering learn mode.  Press any key to hear its function.  " \
  4584.               "To exit learn mode, press the escape key."))
  4585.  
  4586.         # Translators: Orca has a "Learn Mode" that will allow
  4587.         # the user to type any key on the keyboard and hear what
  4588.         # the effects of that key would be.  The effects might
  4589.         # be what Orca would do if it had a handler for the
  4590.         # particular key combination, or they might just be to
  4591.         # echo the name of the key if Orca doesn't have a handler.
  4592.         # This text here is what is to be presented on the braille
  4593.         # display.
  4594.         #
  4595.         braille.displayMessage(_("Learn mode.  Press escape to exit."))
  4596.         settings.learnModeEnabled = True
  4597.         return True
  4598.  
  4599.     def pursueForFlatReview(self, obj):
  4600.         """Determines if we should look any further at the object
  4601.         for flat review."""
  4602.  
  4603.         try:
  4604.             state = obj.getState()
  4605.         except:
  4606.             debug.printException(debug.LEVEL_WARNING)
  4607.             return False
  4608.         else:
  4609.             return state.contains(pyatspi.STATE_SHOWING)
  4610.  
  4611.     def getShowingDescendants(self, parent):
  4612.         """Given a parent that manages its descendants, return a list of
  4613.         Accessible children that are actually showing.  This algorithm
  4614.         was inspired a little by the srw_elements_from_accessible logic
  4615.         in Gnopernicus, and makes the assumption that the children of
  4616.         an object that manages its descendants are arranged in a row
  4617.         and column format.
  4618.  
  4619.         Arguments:
  4620.         - parent: The accessible which manages its descendants
  4621.  
  4622.         Returns a list of Accessible descendants which are showing.
  4623.         """
  4624.  
  4625.         if not parent:
  4626.             return []
  4627.  
  4628.         if not parent.getState().contains(pyatspi.STATE_MANAGES_DESCENDANTS) \
  4629.            or parent.childCount <= 50:
  4630.             return []
  4631.  
  4632.         try:
  4633.             icomponent = parent.queryComponent()
  4634.         except NotImplementedError:
  4635.             return []
  4636.  
  4637.         descendants = []
  4638.  
  4639.         parentExtents = icomponent.getExtents(0)
  4640.  
  4641.         # [[[TODO: WDW - HACK related to GAIL bug where table column
  4642.         # headers seem to be ignored:
  4643.         # http://bugzilla.gnome.org/show_bug.cgi?id=325809.  The
  4644.         # problem is that this causes getAccessibleAtPoint to return
  4645.         # the cell effectively below the real cell at a given point,
  4646.         # making a mess of everything.  So...we just manually add in
  4647.         # showing headers for now.  The remainder of the logic below
  4648.         # accidentally accounts for this offset, yet it should also
  4649.         # work when bug 325809 is fixed.]]]
  4650.         #
  4651.         try:
  4652.             table = parent.queryTable()
  4653.         except NotImplementedError:
  4654.             table = None
  4655.             
  4656.         if table:
  4657.             for i in range(0, table.nColumns):
  4658.                 header = table.getColumnHeader(i)
  4659.                 if header:
  4660.                     extents = header.queryComponent().getExtents(0)
  4661.                     stateset = header.getState()
  4662.                     if stateset.contains(pyatspi.STATE_SHOWING) \
  4663.                        and (extents.x >= 0) and (extents.y >= 0) \
  4664.                        and (extents.width > 0) and (extents.height > 0) \
  4665.                        and self.visible(extents.x, extents.y,
  4666.                                         extents.width, extents.height,
  4667.                                         parentExtents.x, parentExtents.y,
  4668.                                         parentExtents.width,
  4669.                                         parentExtents.height):
  4670.                         descendants.append(header)
  4671.  
  4672.         # This algorithm goes left to right, top to bottom while attempting
  4673.         # to do *some* optimization over queries.  It could definitely be
  4674.         # improved. The gridSize is a minimal chunk to jump around in the
  4675.         # table.
  4676.         #
  4677.         gridSize = 7
  4678.         currentY = parentExtents.y
  4679.         while currentY < (parentExtents.y + parentExtents.height):
  4680.             currentX = parentExtents.x
  4681.             minHeight = sys.maxint
  4682.             while currentX < (parentExtents.x + parentExtents.width):
  4683.                 child = \
  4684.                     icomponent.getAccessibleAtPoint(currentX, currentY + 1, 0)
  4685.                 if child:
  4686.                     extents = child.queryComponent().getExtents(0)
  4687.                     if extents.x >= 0 and extents.y >= 0:
  4688.                         newX = extents.x + extents.width
  4689.                         minHeight = min(minHeight, extents.height)
  4690.                         if not descendants.count(child):
  4691.                             descendants.append(child)
  4692.                     else:
  4693.                         newX = currentX + gridSize
  4694.                 else:
  4695.                     newX = currentX + gridSize
  4696.                 if newX <= currentX:
  4697.                     currentX += gridSize
  4698.                 else:
  4699.                     currentX = newX
  4700.             if minHeight == sys.maxint:
  4701.                 minHeight = gridSize
  4702.             currentY += minHeight
  4703.  
  4704.         return descendants
  4705.  
  4706.     def visible(self,
  4707.                 ax, ay, awidth, aheight,
  4708.                 bx, by, bwidth, bheight):
  4709.         """Returns true if any portion of region 'a' is in region 'b'
  4710.         """
  4711.         highestBottom = min(ay + aheight, by + bheight)
  4712.         lowestTop = max(ay, by)
  4713.  
  4714.         leftMostRightEdge = min(ax + awidth, bx + bwidth)
  4715.         rightMostLeftEdge = max(ax, bx)
  4716.  
  4717.         visible = False
  4718.  
  4719.         if (lowestTop <= highestBottom) \
  4720.            and (rightMostLeftEdge <= leftMostRightEdge):
  4721.             visible = True
  4722.         elif (aheight == 0):
  4723.             if (awidth == 0):
  4724.                 visible = (lowestTop == highestBottom) \
  4725.                           and (leftMostRightEdge == rightMostLeftEdge)
  4726.             else:
  4727.                 visible = leftMostRightEdge <= rightMostLeftEdge
  4728.         elif (awidth == 0):
  4729.             visible = (lowestTop <= highestBottom)
  4730.  
  4731.         return visible
  4732.  
  4733.     def getFlatReviewContext(self):
  4734.         """Returns the flat review context, creating one if necessary."""
  4735.  
  4736.         if not self.flatReviewContext:
  4737.             self.flatReviewContext = self.flatReviewContextClass(self)
  4738.             self.justEnteredFlatReviewMode = True
  4739.  
  4740.             # Remember where the cursor currently was
  4741.             # when the user was in focus tracking mode.  We'll try to
  4742.             # keep the position the same as we move to characters above
  4743.             # and below us.
  4744.             #
  4745.             self.targetCursorCell = braille.cursorCell
  4746.  
  4747.         return self.flatReviewContext
  4748.  
  4749.     def toggleFlatReviewMode(self, inputEvent=None):
  4750.         """Toggles between flat review mode and focus tracking mode."""
  4751.  
  4752.         if self.flatReviewContext:
  4753.             self.drawOutline(-1, 0, 0, 0)
  4754.             self.flatReviewContext = None
  4755.             self.updateBraille(orca_state.locusOfFocus)
  4756.         else:
  4757.             context = self.getFlatReviewContext()
  4758.             [wordString, x, y, width, height] = \
  4759.                      context.getCurrent(flat_review.Context.WORD)
  4760.             self.drawOutline(x, y, width, height)
  4761.             self._reviewCurrentItem(inputEvent, self.targetCursorCell)
  4762.  
  4763.         return True
  4764.  
  4765.     def updateBrailleReview(self, targetCursorCell=0):
  4766.         """Obtains the braille regions for the current flat review line
  4767.         and displays them on the braille display.  If the targetCursorCell
  4768.         is non-0, then an attempt will be made to postion the review cursor
  4769.         at that cell.  Otherwise, we will pan in display-sized increments
  4770.         to show the review cursor."""
  4771.  
  4772.         context = self.getFlatReviewContext()
  4773.  
  4774.         [regions, regionWithFocus] = context.getCurrentBrailleRegions()
  4775.         if not regions:
  4776.             regions = []
  4777.             regionWithFocus = None
  4778.  
  4779.         line = braille.Line()
  4780.         line.addRegions(regions)
  4781.         braille.setLines([line])
  4782.         braille.setFocus(regionWithFocus, False)
  4783.         if regionWithFocus:
  4784.             braille.panToOffset(regionWithFocus.brailleOffset \
  4785.                                 + regionWithFocus.cursorOffset)
  4786.  
  4787.         if self.justEnteredFlatReviewMode:
  4788.             braille.refresh(True, self.targetCursorCell)
  4789.             self.justEnteredFlatReviewMode = False
  4790.         else:
  4791.             braille.refresh(True, targetCursorCell)
  4792.  
  4793.     def _setFlatReviewContextToBeginningOfBrailleDisplay(self):
  4794.         """Sets the character of interest to be the first character showing
  4795.         at the beginning of the braille display."""
  4796.  
  4797.         context = self.getFlatReviewContext()
  4798.         [regions, regionWithFocus] = context.getCurrentBrailleRegions()
  4799.         for region in regions:
  4800.             if ((region.brailleOffset + len(region.string.decode("UTF-8"))) \
  4801.                    > braille.viewport[0]) \
  4802.                 and (isinstance(region, braille.ReviewText) \
  4803.                      or isinstance(region, braille.ReviewComponent)):
  4804.                 position = max(region.brailleOffset, braille.viewport[0])
  4805.                 offset = position - region.brailleOffset
  4806.                 self.targetCursorCell = region.brailleOffset \
  4807.                                         - braille.viewport[0]
  4808.                 [word, charOffset] = region.zone.getWordAtOffset(offset)
  4809.                 if word:
  4810.                     self.flatReviewContext.setCurrent(
  4811.                         word.zone.line.index,
  4812.                         word.zone.index,
  4813.                         word.index,
  4814.                         charOffset)
  4815.                 else:
  4816.                     self.flatReviewContext.setCurrent(
  4817.                         region.zone.line.index,
  4818.                         region.zone.index,
  4819.                         0, # word index
  4820.                         0) # character index
  4821.                 break
  4822.  
  4823.     def panBrailleLeft(self, inputEvent=None, panAmount=0):
  4824.         """Pans the braille display to the left.  If panAmount is non-zero,
  4825.         the display is panned by that many cells.  If it is 0, the display
  4826.         is panned one full display width.  In flat review mode, panning
  4827.         beyond the beginning will take you to the end of the previous line.
  4828.  
  4829.         In focus tracking mode, the cursor stays at its logical position.
  4830.         In flat review mode, the review cursor moves to character
  4831.         associated with cell 0."""
  4832.  
  4833.         if self.flatReviewContext:
  4834.             if braille.beginningIsShowing:
  4835.                 self.flatReviewContext.goBegin(flat_review.Context.LINE)
  4836.                 self.reviewPreviousCharacter(inputEvent)
  4837.             else:
  4838.                 braille.panLeft(panAmount)
  4839.  
  4840.             # This will update our target cursor cell
  4841.             #
  4842.             self._setFlatReviewContextToBeginningOfBrailleDisplay()
  4843.  
  4844.             [charString, x, y, width, height] = \
  4845.                 self.flatReviewContext.getCurrent(flat_review.Context.CHAR)
  4846.             self.drawOutline(x, y, width, height)
  4847.  
  4848.             self.targetCursorCell = 1
  4849.             self.updateBrailleReview(self.targetCursorCell)
  4850.         elif braille.beginningIsShowing and orca_state.locusOfFocus \
  4851.              and self.isTextArea(orca_state.locusOfFocus):
  4852.  
  4853.             # If we're at the beginning of a line of a multiline text
  4854.             # area, then force it's caret to the end of the previous
  4855.             # line.  The assumption here is that we're currently
  4856.             # viewing the line that has the caret -- which is a pretty
  4857.             # good assumption for focus tacking mode.  When we set the
  4858.             # caret position, we will get a caret event, which will
  4859.             # then update the braille.
  4860.             #
  4861.             text = orca_state.locusOfFocus.queryText()
  4862.             [lineString, startOffset, endOffset] = text.getTextAtOffset(
  4863.                 text.caretOffset,
  4864.                 pyatspi.TEXT_BOUNDARY_LINE_START)
  4865.             movedCaret = False
  4866.             if startOffset > 0:
  4867.                 movedCaret = text.setCaretOffset(startOffset - 1)
  4868.  
  4869.             # If we didn't move the caret and we're in a terminal, we
  4870.             # jump into flat review to review the text.  See
  4871.             # http://bugzilla.gnome.org/show_bug.cgi?id=482294.
  4872.             #
  4873.             if (not movedCaret) \
  4874.                and (orca_state.locusOfFocus.getRole() \
  4875.                     == pyatspi.ROLE_TERMINAL):
  4876.                 context = self.getFlatReviewContext()
  4877.                 context.goBegin(flat_review.Context.LINE)
  4878.                 self.reviewPreviousCharacter(inputEvent)
  4879.         else:
  4880.             braille.panLeft(panAmount)
  4881.             braille.refresh(False)
  4882.  
  4883.         return True
  4884.  
  4885.     def panBrailleLeftOneChar(self, inputEvent=None):
  4886.         """Nudges the braille display one character to the left.
  4887.  
  4888.         In focus tracking mode, the cursor stays at its logical position.
  4889.         In flat review mode, the review cursor moves to character
  4890.         associated with cell 0."""
  4891.  
  4892.         self.panBrailleLeft(inputEvent, 1)
  4893.  
  4894.     def panBrailleRight(self, inputEvent=None, panAmount=0):
  4895.         """Pans the braille display to the right.  If panAmount is non-zero,
  4896.         the display is panned by that many cells.  If it is 0, the display
  4897.         is panned one full display width.  In flat review mode, panning
  4898.         beyond the end will take you to the begininng of the next line.
  4899.  
  4900.         In focus tracking mode, the cursor stays at its logical position.
  4901.         In flat review mode, the review cursor moves to character
  4902.         associated with cell 0."""
  4903.  
  4904.         if self.flatReviewContext:
  4905.             if braille.endIsShowing:
  4906.                 self.flatReviewContext.goEnd(flat_review.Context.LINE)
  4907.                 self.reviewNextCharacter(inputEvent)
  4908.             else:
  4909.                 braille.panRight(panAmount)
  4910.  
  4911.             # This will update our target cursor cell
  4912.             #
  4913.             self._setFlatReviewContextToBeginningOfBrailleDisplay()
  4914.  
  4915.             [charString, x, y, width, height] = \
  4916.                 self.flatReviewContext.getCurrent(flat_review.Context.CHAR)
  4917.  
  4918.             self.drawOutline(x, y, width, height)
  4919.  
  4920.             self.targetCursorCell = 1
  4921.             self.updateBrailleReview(self.targetCursorCell)
  4922.         elif braille.endIsShowing and orca_state.locusOfFocus \
  4923.              and self.isTextArea(orca_state.locusOfFocus):
  4924.             # If we're at the end of a line of a multiline text area, then
  4925.             # force it's caret to the beginning of the next line.  The
  4926.             # assumption here is that we're currently viewing the line that
  4927.             # has the caret -- which is a pretty good assumption for focus
  4928.             # tacking mode.  When we set the caret position, we will get a
  4929.             # caret event, which will then update the braille.
  4930.             #
  4931.             text = orca_state.locusOfFocus.queryText()
  4932.             [lineString, startOffset, endOffset] = text.getTextAtOffset(
  4933.                 text.caretOffset,
  4934.                 pyatspi.TEXT_BOUNDARY_LINE_START)
  4935.             if endOffset < text.characterCount:
  4936.                 text.setCaretOffset(endOffset)
  4937.         else:
  4938.             braille.panRight(panAmount)
  4939.             braille.refresh(False)
  4940.  
  4941.         return True
  4942.  
  4943.     def panBrailleRightOneChar(self, inputEvent=None):
  4944.         """Nudges the braille display one character to the right.
  4945.  
  4946.         In focus tracking mode, the cursor stays at its logical position.
  4947.         In flat review mode, the review cursor moves to character
  4948.         associated with cell 0."""
  4949.  
  4950.         self.panBrailleRight(inputEvent, 1)
  4951.  
  4952.     def goBrailleHome(self, inputEvent=None):
  4953.         """Returns to the component with focus."""
  4954.  
  4955.         if self.flatReviewContext:
  4956.             return self.toggleFlatReviewMode(inputEvent)
  4957.         else:
  4958.             return braille.returnToRegionWithFocus(inputEvent)
  4959.  
  4960.     def leftClickReviewItem(self, inputEvent=None):
  4961.         """Performs a left mouse button click on the current item."""
  4962.  
  4963.         self.getFlatReviewContext().clickCurrent(1)
  4964.         return True
  4965.  
  4966.     def rightClickReviewItem(self, inputEvent=None):
  4967.         """Performs a right mouse button click on the current item."""
  4968.  
  4969.         self.getFlatReviewContext().clickCurrent(3)
  4970.         return True
  4971.  
  4972.     def reviewCurrentLine(self, inputEvent):
  4973.         """Brailles and speaks the current flat review line."""
  4974.  
  4975.         self._reviewCurrentLine(inputEvent, 1)
  4976.         self.lastReviewCurrentEvent = inputEvent
  4977.  
  4978.         return True
  4979.  
  4980.     def reviewSpellCurrentLine(self, inputEvent):
  4981.         """Brailles and spells the current flat review line."""
  4982.  
  4983.         self._reviewCurrentLine(inputEvent, 2)
  4984.         self.lastReviewCurrentEvent = inputEvent
  4985.  
  4986.         return True
  4987.  
  4988.     def reviewPhoneticCurrentLine(self, inputEvent):
  4989.         """Brailles and phonetically spells the current flat review line."""
  4990.  
  4991.         self._reviewCurrentLine(inputEvent, 3)
  4992.         self.lastReviewCurrentEvent = inputEvent
  4993.  
  4994.         return True
  4995.  
  4996.     def _reviewCurrentLine(self, inputEvent, speechType=1):
  4997.         """Presents the current flat review line via braille and speech.
  4998.  
  4999.         Arguments:
  5000.         - inputEvent - the current input event.
  5001.         - speechType - the desired presentation: speak (1), spell (2), or
  5002.                        phonetic (3)
  5003.         """
  5004.  
  5005.         context = self.getFlatReviewContext()
  5006.  
  5007.         [lineString, x, y, width, height] = \
  5008.                  context.getCurrent(flat_review.Context.LINE)
  5009.         self.drawOutline(x, y, width, height)
  5010.  
  5011.         # Don't announce anything from speech if the user used
  5012.         # the Braille display as an input device.
  5013.         #
  5014.         if not isinstance(inputEvent, input_event.BrailleEvent):
  5015.             if (not lineString) \
  5016.                or (not len(lineString)) \
  5017.                or (lineString == "\n"):
  5018.                 # Translators: "blank" is a short word to mean the
  5019.                 # user has navigated to an empty line.
  5020.                 #
  5021.                 speech.speak(_("blank"))
  5022.             elif lineString.isspace():
  5023.                 # Translators: "white space" is a short phrase to mean the
  5024.                 # user has navigated to a line with only whitespace on it.
  5025.                 #
  5026.                 speech.speak(_("white space"))
  5027.             elif lineString.isupper() and (speechType < 2 or speechType > 3):
  5028.                 speech.speak(lineString, self.voices[settings.UPPERCASE_VOICE])
  5029.             elif speechType == 2:
  5030.                 self.spellCurrentItem(lineString)
  5031.             elif speechType == 3:
  5032.                 self.phoneticSpellCurrentItem(lineString)
  5033.             else:
  5034.                 lineString = self.adjustForRepeats(lineString)
  5035.                 speech.speak(lineString)
  5036.  
  5037.         self.updateBrailleReview()
  5038.  
  5039.         return True
  5040.  
  5041.     def reviewPreviousLine(self, inputEvent):
  5042.         """Moves the flat review context to the beginning of the
  5043.         previous line."""
  5044.  
  5045.         context = self.getFlatReviewContext()
  5046.  
  5047.         moved = context.goPrevious(flat_review.Context.LINE,
  5048.                                    flat_review.Context.WRAP_LINE)
  5049.  
  5050.         if moved:
  5051.             self._reviewCurrentLine(inputEvent)
  5052.             self.targetCursorCell = braille.cursorCell
  5053.  
  5054.         return True
  5055.  
  5056.     def reviewHome(self, inputEvent):
  5057.         """Moves the flat review context to the top left of the current
  5058.         window."""
  5059.  
  5060.         context = self.getFlatReviewContext()
  5061.  
  5062.         context.goBegin()
  5063.  
  5064.         self._reviewCurrentLine(inputEvent)
  5065.         self.targetCursorCell = braille.cursorCell
  5066.  
  5067.         return True
  5068.  
  5069.     def reviewNextLine(self, inputEvent):
  5070.         """Moves the flat review context to the beginning of the
  5071.         next line.  Places the flat review cursor at the beginning
  5072.         of the line."""
  5073.  
  5074.         context = self.getFlatReviewContext()
  5075.  
  5076.         moved = context.goNext(flat_review.Context.LINE,
  5077.                                flat_review.Context.WRAP_LINE)
  5078.  
  5079.         if moved:
  5080.             self._reviewCurrentLine(inputEvent)
  5081.             self.targetCursorCell = braille.cursorCell
  5082.  
  5083.         return True
  5084.  
  5085.     def reviewBottomLeft(self, inputEvent):
  5086.         """Moves the flat review context to the beginning of the
  5087.         last line in the window.  Places the flat review cursor at
  5088.         the beginning of the line."""
  5089.  
  5090.         context = self.getFlatReviewContext()
  5091.  
  5092.         context.goEnd(flat_review.Context.WINDOW)
  5093.         context.goBegin(flat_review.Context.LINE)
  5094.         self._reviewCurrentLine(inputEvent)
  5095.         self.targetCursorCell = braille.cursorCell
  5096.  
  5097.         return True
  5098.  
  5099.     def reviewEnd(self, inputEvent):
  5100.         """Moves the flat review context to the end of the
  5101.         last line in the window.  Places the flat review cursor
  5102.         at the end of the line."""
  5103.  
  5104.         context = self.getFlatReviewContext()
  5105.         context.goEnd()
  5106.  
  5107.         self._reviewCurrentLine(inputEvent)
  5108.         self.targetCursorCell = braille.cursorCell
  5109.  
  5110.         return True
  5111.  
  5112.     def reviewCurrentItem(self, inputEvent, targetCursorCell=0):
  5113.         """Brailles and speaks the current item to the user."""
  5114.  
  5115.         self._reviewCurrentItem(inputEvent, targetCursorCell, 1)
  5116.         self.lastReviewCurrentEvent = inputEvent
  5117.  
  5118.         return True
  5119.  
  5120.     def reviewSpellCurrentItem(self, inputEvent, targetCursorCell=0):
  5121.         """Brailles and spells the current item to the user."""
  5122.  
  5123.         self._reviewCurrentItem(inputEvent, targetCursorCell, 2)
  5124.         self.lastReviewCurrentEvent = inputEvent
  5125.  
  5126.         return True
  5127.  
  5128.     def reviewPhoneticCurrentItem(self, inputEvent, targetCursorCell=0):
  5129.         """Brailles and phonetically spells the current item to the user."""
  5130.  
  5131.         self._reviewCurrentItem(inputEvent, targetCursorCell, 3)
  5132.         self.lastReviewCurrentEvent = inputEvent
  5133.  
  5134.         return True
  5135.  
  5136.     def spellCurrentItem(self, itemString):
  5137.         """Spell the current flat review word or line.
  5138.  
  5139.         Arguments:
  5140.         - itemString: the string to spell.
  5141.         """
  5142.  
  5143.         for (charIndex, character) in enumerate(itemString.decode("UTF-8")):
  5144.             if character.isupper():
  5145.                 speech.speak(character.encode("UTF-8"),
  5146.                              self.voices[settings.UPPERCASE_VOICE])
  5147.             else:
  5148.                 speech.speak(character.encode("UTF-8"))
  5149.  
  5150.     def _reviewCurrentItem(self, inputEvent, targetCursorCell=0,
  5151.                            speechType=1):
  5152.         """Presents the current item to the user.
  5153.  
  5154.         Arguments:
  5155.         - inputEvent - the current input event.
  5156.         - targetCursorCell - if non-zero, the target braille cursor cell.
  5157.         - speechType - the desired presentation: speak (1), spell (2), or
  5158.                        phonetic (3).
  5159.         """
  5160.  
  5161.         context = self.getFlatReviewContext()
  5162.         [wordString, x, y, width, height] = \
  5163.                  context.getCurrent(flat_review.Context.WORD)
  5164.         self.drawOutline(x, y, width, height)
  5165.  
  5166.         # Don't announce anything from speech if the user used
  5167.         # the Braille display as an input device.
  5168.         #
  5169.         if not isinstance(inputEvent, input_event.BrailleEvent):
  5170.             if (not wordString) \
  5171.                or (not len(wordString)) \
  5172.                or (wordString == "\n"):
  5173.                 # Translators: "blank" is a short word to mean the
  5174.                 # user has navigated to an empty line.
  5175.                 #
  5176.                 speech.speak(_("blank"))
  5177.             else:
  5178.                 [lineString, x, y, width, height] = \
  5179.                          context.getCurrent(flat_review.Context.LINE)
  5180.                 if lineString == "\n":
  5181.                     # Translators: "blank" is a short word to mean the
  5182.                     # user has navigated to an empty line.
  5183.                     #
  5184.                     speech.speak(_("blank"))
  5185.                 elif wordString.isspace():
  5186.                     # Translators: "white space" is a short phrase to mean the
  5187.                     # user has navigated to a line with only whitespace on it.
  5188.                     #
  5189.                     speech.speak(_("white space"))
  5190.                 elif wordString.isupper() and speechType == 1:
  5191.                     speech.speak(wordString,
  5192.                                  self.voices[settings.UPPERCASE_VOICE])
  5193.                 elif speechType == 2:
  5194.                     self.spellCurrentItem(wordString)
  5195.                 elif speechType == 3:
  5196.                     self.phoneticSpellCurrentItem(wordString)
  5197.                 elif speechType == 1:
  5198.                     wordString = self.adjustForRepeats(wordString)
  5199.                     speech.speak(wordString)
  5200.  
  5201.         self.updateBrailleReview(targetCursorCell)
  5202.  
  5203.         return True
  5204.  
  5205.     def reviewCurrentAccessible(self, inputEvent):
  5206.         context = self.getFlatReviewContext()
  5207.         [zoneString, x, y, width, height] = \
  5208.                  context.getCurrent(flat_review.Context.ZONE)
  5209.         self.drawOutline(x, y, width, height)
  5210.  
  5211.         # Don't announce anything from speech if the user used
  5212.         # the Braille display as an input device.
  5213.         #
  5214.         if not isinstance(inputEvent, input_event.BrailleEvent):
  5215.             utterances = self.speechGenerator.getSpeech(
  5216.                     context.getCurrentAccessible(), False)
  5217.             utterances.extend(self.tutorialGenerator.getTutorial(
  5218.                     context.getCurrentAccessible(), False))
  5219.             speech.speakUtterances(utterances)
  5220.         return True
  5221.  
  5222.     def reviewPreviousItem(self, inputEvent):
  5223.         """Moves the flat review context to the previous item.  Places
  5224.         the flat review cursor at the beginning of the item."""
  5225.  
  5226.         context = self.getFlatReviewContext()
  5227.  
  5228.         moved = context.goPrevious(flat_review.Context.WORD,
  5229.                                    flat_review.Context.WRAP_LINE)
  5230.  
  5231.         if moved:
  5232.             self._reviewCurrentItem(inputEvent)
  5233.             self.targetCursorCell = braille.cursorCell
  5234.  
  5235.         return True
  5236.  
  5237.     def reviewNextItem(self, inputEvent):
  5238.         """Moves the flat review context to the next item.  Places
  5239.         the flat review cursor at the beginning of the item."""
  5240.  
  5241.         context = self.getFlatReviewContext()
  5242.  
  5243.         moved = context.goNext(flat_review.Context.WORD,
  5244.                                flat_review.Context.WRAP_LINE)
  5245.  
  5246.         if moved:
  5247.             self._reviewCurrentItem(inputEvent)
  5248.             self.targetCursorCell = braille.cursorCell
  5249.  
  5250.         return True
  5251.  
  5252.     def reviewCurrentCharacter(self, inputEvent):
  5253.         """Brailles and speaks the current flat review character."""
  5254.  
  5255.         self._reviewCurrentCharacter(inputEvent, 1)
  5256.         self.lastReviewCurrentEvent = inputEvent
  5257.  
  5258.         return True
  5259.  
  5260.     def reviewSpellCurrentCharacter(self, inputEvent):
  5261.         """Brailles and 'spells' (phonetically) the current flat review
  5262.         character.
  5263.         """
  5264.  
  5265.         self._reviewCurrentCharacter(inputEvent, 2)
  5266.         self.lastReviewCurrentEvent = inputEvent
  5267.  
  5268.         return True
  5269.  
  5270.     def _reviewCurrentCharacter(self, inputEvent, speechType=1):
  5271.         """Presents the current flat review character via braille and speech.
  5272.  
  5273.         Arguments:
  5274.         - inputEvent - the current input event.
  5275.         - speechType - the desired presentation: speak (1) or phonetic (2)
  5276.         """
  5277.  
  5278.         context = self.getFlatReviewContext()
  5279.  
  5280.         [charString, x, y, width, height] = \
  5281.                  context.getCurrent(flat_review.Context.CHAR)
  5282.         self.drawOutline(x, y, width, height)
  5283.  
  5284.         # Don't announce anything from speech if the user used
  5285.         # the Braille display as an input device.
  5286.         #
  5287.         if not isinstance(inputEvent, input_event.BrailleEvent):
  5288.             if (not charString) or (not len(charString)):
  5289.                 # Translators: "blank" is a short word to mean the
  5290.                 # user has navigated to an empty line.
  5291.                 #
  5292.                 speech.speak(_("blank"))
  5293.             else:
  5294.                 [lineString, x, y, width, height] = \
  5295.                          context.getCurrent(flat_review.Context.LINE)
  5296.                 if lineString == "\n":
  5297.                     # Translators: "blank" is a short word to mean the
  5298.                     # user has navigated to an empty line.
  5299.                     #
  5300.                     speech.speak(_("blank"))
  5301.                 elif speechType == 2:
  5302.                     self.phoneticSpellCurrentItem(charString)
  5303.                 elif charString.decode("UTF-8").isupper():
  5304.                     speech.speakCharacter(charString,
  5305.                                           self.voices[settings.UPPERCASE_VOICE])
  5306.                 else:
  5307.                     speech.speakCharacter(charString)
  5308.  
  5309.         self.updateBrailleReview()
  5310.  
  5311.         return True
  5312.  
  5313.     def reviewPreviousCharacter(self, inputEvent):
  5314.         """Moves the flat review context to the previous character.  Places
  5315.         the flat review cursor at character."""
  5316.  
  5317.         context = self.getFlatReviewContext()
  5318.  
  5319.         moved = context.goPrevious(flat_review.Context.CHAR,
  5320.                                    flat_review.Context.WRAP_LINE)
  5321.  
  5322.         if moved:
  5323.             self._reviewCurrentCharacter(inputEvent)
  5324.             self.targetCursorCell = braille.cursorCell
  5325.  
  5326.         return True
  5327.  
  5328.     def reviewEndOfLine(self, inputEvent):
  5329.         """Moves the flat review context to the end of the line.  Places
  5330.         the flat review cursor at the end of the line."""
  5331.  
  5332.         context = self.getFlatReviewContext()
  5333.         context.goEnd(flat_review.Context.LINE)
  5334.  
  5335.         self.reviewCurrentCharacter(inputEvent)
  5336.         self.targetCursorCell = braille.cursorCell
  5337.  
  5338.         return True
  5339.  
  5340.     def reviewNextCharacter(self, inputEvent):
  5341.         """Moves the flat review context to the next character.  Places
  5342.         the flat review cursor at character."""
  5343.  
  5344.         context = self.getFlatReviewContext()
  5345.  
  5346.         moved = context.goNext(flat_review.Context.CHAR,
  5347.                                flat_review.Context.WRAP_LINE)
  5348.  
  5349.         if moved:
  5350.             self._reviewCurrentCharacter(inputEvent)
  5351.             self.targetCursorCell = braille.cursorCell
  5352.  
  5353.         return True
  5354.  
  5355.     def reviewAbove(self, inputEvent):
  5356.         """Moves the flat review context to the character most directly
  5357.         above the current flat review cursor.  Places the flat review
  5358.         cursor at character."""
  5359.  
  5360.         context = self.getFlatReviewContext()
  5361.  
  5362.         moved = context.goAbove(flat_review.Context.CHAR,
  5363.                                 flat_review.Context.WRAP_LINE)
  5364.  
  5365.         if moved:
  5366.             self._reviewCurrentItem(inputEvent, self.targetCursorCell)
  5367.  
  5368.         return True
  5369.  
  5370.     def reviewBelow(self, inputEvent):
  5371.         """Moves the flat review context to the character most directly
  5372.         below the current flat review cursor.  Places the flat review
  5373.         cursor at character."""
  5374.  
  5375.         context = self.getFlatReviewContext()
  5376.  
  5377.         moved = context.goBelow(flat_review.Context.CHAR,
  5378.                                 flat_review.Context.WRAP_LINE)
  5379.  
  5380.         if moved:
  5381.             self._reviewCurrentItem(inputEvent, self.targetCursorCell)
  5382.  
  5383.         return True
  5384.  
  5385.     def showZones(self, inputEvent):
  5386.         """Debug routine to paint rectangles around the discrete
  5387.         interesting (e.g., text)  zones in the active window for
  5388.         this application.
  5389.         """
  5390.  
  5391.         flatReviewContext = self.getFlatReviewContext()
  5392.         lines = flatReviewContext.lines
  5393.         for line in lines:
  5394.             lineString = ""
  5395.             for zone in line.zones:
  5396.                 lineString += " '%s' [%s]" % \
  5397.                           (zone.string, zone.accessible.getRoleName())
  5398.             debug.println(debug.LEVEL_OFF, lineString)
  5399.         self.flatReviewContext = None
  5400.  
  5401.     def find(self, query=None):
  5402.         """Searches for the specified query.  If no query is specified,
  5403.         it searches for the query specified in the Orca Find dialog.
  5404.  
  5405.         Arguments:
  5406.         - query: The search query to find.
  5407.         """
  5408.  
  5409.         if not query:
  5410.             query = find.getLastQuery()
  5411.         if query:
  5412.             context = self.getFlatReviewContext()
  5413.             location = query.findQuery(context, self.justEnteredFlatReviewMode)
  5414.             if not location:
  5415.                 # Translators: the Orca "Find" dialog allows a user to
  5416.                 # search for text in a window and then move focus to
  5417.                 # that text.  For example, they may want to find the
  5418.                 # "OK" button.  This message lets them know a string
  5419.                 # they were searching for was not found.
  5420.                 #
  5421.                 message = _("string not found")
  5422.                 braille.displayMessage(message)
  5423.                 speech.speak(message)
  5424.             else:
  5425.                 context.setCurrent(location.lineIndex, location.zoneIndex, \
  5426.                                    location.wordIndex, location.charIndex)
  5427.                 self.reviewCurrentItem(None)
  5428.                 self.targetCursorCell = braille.cursorCell
  5429.  
  5430.     def findNext(self, inputEvent):
  5431.         """Searches forward for the next instance of the string
  5432.         searched for via the Orca Find dialog.  Other than direction
  5433.         and the starting point, the search options initially specified
  5434.         (case sensitivity, window wrap, and full/partial match) are
  5435.         preserved.
  5436.         """
  5437.  
  5438.         lastQuery = find.getLastQuery()
  5439.         if lastQuery:
  5440.             lastQuery.searchBackwards = False
  5441.             lastQuery.startAtTop = False
  5442.             self.find(lastQuery)
  5443.         else:
  5444.             orca.showFindGUI()
  5445.  
  5446.     def findPrevious(self, inputEvent):
  5447.         """Searches backwards for the next instance of the string
  5448.         searched for via the Orca Find dialog.  Other than direction
  5449.         and the starting point, the search options initially specified
  5450.         (case sensitivity, window wrap, and full/or partial match) are
  5451.         preserved.
  5452.         """
  5453.  
  5454.         lastQuery = find.getLastQuery()
  5455.         if lastQuery:
  5456.             lastQuery.searchBackwards = True
  5457.             lastQuery.startAtTop = False
  5458.             self.find(lastQuery)
  5459.         else:
  5460.             orca.showFindGUI()
  5461.  
  5462.     def goToBookmark(self, inputEvent):
  5463.         """ Go to the bookmark indexed by inputEvent.hw_code.  Delegates to
  5464.         Bookmark.goToBookmark """
  5465.         bookmarks = self.getBookmarks()
  5466.         bookmarks.goToBookmark(inputEvent)
  5467.  
  5468.     def addBookmark(self, inputEvent):
  5469.         """ Add an in-page accessible object bookmark for this key.
  5470.         Delegates to Bookmark.addBookmark """
  5471.         bookmarks = self.getBookmarks()
  5472.         bookmarks.addBookmark(inputEvent)
  5473.  
  5474.     def bookmarkCurrentWhereAmI(self, inputEvent):
  5475.         """ Report "Where am I" information for this bookmark relative to the
  5476.         current pointer location.  Delegates to
  5477.         Bookmark.bookmarkCurrentWhereAmI"""
  5478.         bookmarks = self.getBookmarks()
  5479.         bookmarks.bookmarkCurrentWhereAmI(inputEvent)
  5480.  
  5481.     def saveBookmarks(self, inputEvent):
  5482.         """ Save the bookmarks for this script. Delegates to
  5483.         Bookmark.saveBookmarks """
  5484.         bookmarks = self.getBookmarks()
  5485.         bookmarks.saveBookmarks(inputEvent)
  5486.  
  5487.     def goToNextBookmark(self, inputEvent):
  5488.         """ Go to the next bookmark location.  If no bookmark has yet to be
  5489.         selected, the first bookmark will be used.  Delegates to
  5490.         Bookmark.goToNextBookmark """
  5491.         bookmarks = self.getBookmarks()
  5492.         bookmarks.goToNextBookmark(inputEvent)
  5493.  
  5494.     def goToPrevBookmark(self, inputEvent):
  5495.         """ Go to the previous bookmark location.  If no bookmark has yet to
  5496.         be selected, the first bookmark will be used.  Delegates to
  5497.         Bookmark.goToPrevBookmark """
  5498.         bookmarks = self.getBookmarks()
  5499.         bookmarks.goToPrevBookmark(inputEvent)
  5500.  
  5501. ########################################################################
  5502. #                                                                      #
  5503. # DEBUG support.                                                       #
  5504. #                                                                      #
  5505. ########################################################################
  5506.  
  5507.     def _isInterestingObj(self, obj):
  5508.         import inspect
  5509.  
  5510.         interesting = False
  5511.  
  5512.         if getattr(obj, "__class__", None):
  5513.             name = obj.__class__.__name__
  5514.             if name not in ["function",
  5515.                             "type",
  5516.                             "list",
  5517.                             "dict",
  5518.                             "tuple",
  5519.                             "wrapper_descriptor",
  5520.                             "module",
  5521.                             "method_descriptor",
  5522.                             "member_descriptor",
  5523.                             "instancemethod",
  5524.                             "builtin_function_or_method",
  5525.                             "frame",
  5526.                             "classmethod",
  5527.                             "classmethod_descriptor",
  5528.                             "_Environ",
  5529.                             "MemoryError",
  5530.                             "_Printer",
  5531.                             "_Helper",
  5532.                             "getset_descriptor",
  5533.                             "weakref",
  5534.                             "property",
  5535.                             "cell",
  5536.                             "staticmethod",
  5537.                             "EventListener",
  5538.                             "KeystrokeListener",
  5539.                             "KeyBinding",
  5540.                             "InputEventHandler",
  5541.                             "Rolename"]:
  5542.                 try:
  5543.                     filename = inspect.getabsfile(obj.__class__)
  5544.                     if filename.index("orca"):
  5545.                         interesting = True
  5546.                 except:
  5547.                     pass
  5548.  
  5549.         return interesting
  5550.  
  5551.     def _detectCycle(self, obj, visitedObjs, indent=""):
  5552.         """Attempts to discover a cycle in object references."""
  5553.  
  5554.         # [[[TODO: WDW - not sure this really works.]]]
  5555.  
  5556.         import gc
  5557.         visitedObjs.append(obj)
  5558.         for referent in gc.get_referents(obj):
  5559.             try:
  5560.                 if visitedObjs.index(referent):
  5561.                     if self._isInterestingObj(referent):
  5562.                         print indent, "CYCLE!!!!", `referent`
  5563.                     break
  5564.             except:
  5565.                 pass
  5566.             self._detectCycle(referent, visitedObjs, " " + indent)
  5567.         visitedObjs.remove(obj)
  5568.  
  5569.     def _printObjInfo(self, indent, obj):
  5570.         """Prints information about an object, if we care about it."""
  5571.         if self._isInterestingObj(obj):
  5572.             print indent, obj.__class__.__name__, `obj`
  5573.  
  5574.     def printMemoryUsageHandler(self, inputEvent):
  5575.         """Prints memory usage information."""
  5576.         print 'TODO: print something useful for memory debugging'
  5577.  
  5578.     def printAppsHandler(self, inputEvent=None):
  5579.         """Prints a list of all applications to stdout."""
  5580.         self.printApps()
  5581.         return True
  5582.  
  5583.     def printActiveAppHandler(self, inputEvent=None):
  5584.         """Prints the currently active application."""
  5585.         self.printActiveApp()
  5586.         return True
  5587.  
  5588.     def printAncestryHandler(self, inputEvent=None):
  5589.         """Prints the ancestry for the current locusOfFocus"""
  5590.         self.printAncestry(orca_state.locusOfFocus)
  5591.         return True
  5592.  
  5593.     def printHierarchyHandler(self, inputEvent=None):
  5594.         """Prints the application for the current locusOfFocus"""
  5595.         if orca_state.locusOfFocus:
  5596.             self.printHierarchy(orca_state.locusOfFocus.getApplication(),
  5597.                                 orca_state.locusOfFocus)
  5598.         return True
  5599.  
  5600. # Routines that were previously in util.py, but that have now been moved
  5601. # here so that they can be customized in application scripts if so desired.
  5602. #
  5603.  
  5604.     def isSameObject(self, obj1, obj2):
  5605.         if (obj1 == obj2):
  5606.             return True
  5607.         elif (not obj1) or (not obj2):
  5608.             return False
  5609.  
  5610.         try:
  5611.             if (obj1.name != obj2.name) or (obj1.getRole() != obj2.getRole()):
  5612.                 return False
  5613.             else:
  5614.                 # Gecko sometimes creates multiple accessibles to represent
  5615.                 # the same object.  If the two objects have the same name
  5616.                 # and the same role, check the extents.  If those also match
  5617.                 # then the two objects are for all intents and purposes the
  5618.                 # same object.
  5619.                 #
  5620.                 extents1 = \
  5621.                     obj1.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
  5622.                 extents2 = \
  5623.                     obj2.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
  5624.                 if (extents1.x == extents2.x) and \
  5625.                    (extents1.y == extents2.y) and \
  5626.                    (extents1.width == extents2.width) and \
  5627.                    (extents1.height == extents2.height):
  5628.                     return True
  5629.  
  5630.             # When we're looking at children of objects that manage
  5631.             # their descendants, we will often get different objects
  5632.             # that point to the same logical child.  We want to be able
  5633.             # to determine if two objects are in fact pointing to the
  5634.             # same child.
  5635.             # If we cannot do so easily (i.e., object equivalence), we examine
  5636.             # the hierarchy and the object index at each level.
  5637.             #
  5638.             parent1 = obj1
  5639.             parent2 = obj2
  5640.             while (parent1 and parent2 and \
  5641.                     parent1.getState().contains( \
  5642.                         pyatspi.STATE_TRANSIENT) and \
  5643.                     parent2.getState().contains(pyatspi.STATE_TRANSIENT)):
  5644.                 if parent1.getIndexInParent() != parent2.getIndexInParent():
  5645.                     return False
  5646.                 parent1 = parent1.parent
  5647.                 parent2 = parent2.parent
  5648.             if parent1 and parent2 and parent1 == parent2:
  5649.                 return self.getRealActiveDescendant(obj1).name == \
  5650.                        self.getRealActiveDescendant(obj2).name
  5651.         except:
  5652.             pass
  5653.  
  5654.         # In java applications, TRANSIENT state is missing for tree items
  5655.         # (fix for bug #352250)
  5656.         #
  5657.         try:
  5658.             parent1 = obj1
  5659.             parent2 = obj2
  5660.             while parent1 and parent2 and \
  5661.                     parent1.getRole() == pyatspi.ROLE_LABEL and \
  5662.                     parent2.getRole() == pyatspi.ROLE_LABEL:
  5663.                 if parent1.getIndexInParent() != parent2.getIndexInParent():
  5664.                     return False
  5665.                 parent1 = parent1.parent
  5666.                 parent2 = parent2.parent
  5667.             if parent1 and parent2 and parent1 == parent2:
  5668.                 return True
  5669.         except:
  5670.             pass
  5671.  
  5672.         return False
  5673.  
  5674.     def appendString(self, text, newText, delimiter=" "):
  5675.         """Appends the newText to the given text with the delimiter in between
  5676.         and returns the new string.  Edge cases, such as no initial text or
  5677.         no newText, are handled gracefully."""
  5678.  
  5679.         if (not newText) or (len(newText) == 0):
  5680.             return text
  5681.         elif text and len(text):
  5682.             return text + delimiter + newText
  5683.         else:
  5684.             return newText
  5685.  
  5686.     def __hasLabelForRelation(self, label):
  5687.         """Check if label has a LABEL_FOR relation
  5688.  
  5689.         Arguments:
  5690.         - label: the label in question
  5691.  
  5692.         Returns TRUE if label has a LABEL_FOR relation.
  5693.         """
  5694.         if (not label) or (label.getRole() != pyatspi.ROLE_LABEL):
  5695.             return False
  5696.  
  5697.         relations = label.getRelationSet()
  5698.  
  5699.         for relation in relations:
  5700.             if relation.getRelationType() \
  5701.                    == pyatspi.RELATION_LABEL_FOR:
  5702.                 return True
  5703.  
  5704.         return False
  5705.  
  5706.     def __isLabeling(self, label, obj):
  5707.         """Check if label is connected via  LABEL_FOR relation with object
  5708.  
  5709.         Arguments:
  5710.         - obj: the object in question
  5711.         - labeled: the label in question
  5712.  
  5713.         Returns TRUE if label has a relation LABEL_FOR for object.
  5714.         """
  5715.  
  5716.         if (not obj) \
  5717.            or (not label) \
  5718.            or (label.getRole() != pyatspi.ROLE_LABEL):
  5719.             return False
  5720.  
  5721.         relations = label.getRelationSet()
  5722.         if not relations:
  5723.             return False
  5724.  
  5725.         for relation in relations:
  5726.             if relation.getRelationType() \
  5727.                    == pyatspi.RELATION_LABEL_FOR:
  5728.  
  5729.                 for i in range(0, relation.getNTargets()):
  5730.                     target = relation.getTarget(i)
  5731.                     if target == obj:
  5732.                         return True
  5733.  
  5734.         return False
  5735.  
  5736.     def getUnicodeCurrencySymbols(self):
  5737.         """Return a list of the unicode currency symbols, populating the list
  5738.         if this is the first time that this routine has been called.
  5739.  
  5740.         Returns a list of unicode currency symbols.
  5741.         """
  5742.  
  5743.         if not self._unicodeCurrencySymbols:
  5744.             self._unicodeCurrencySymbols = [ \
  5745.                 u'\u0024',     # dollar sign
  5746.                 u'\u00A2',     # cent sign
  5747.                 u'\u00A3',     # pound sign
  5748.                 u'\u00A4',     # currency sign
  5749.                 u'\u00A5',     # yen sign
  5750.                 u'\u0192',     # latin small letter f with hook
  5751.                 u'\u060B',     # afghani sign
  5752.                 u'\u09F2',     # bengali rupee mark
  5753.                 u'\u09F3',     # bengali rupee sign
  5754.                 u'\u0AF1',     # gujarati rupee sign
  5755.                 u'\u0BF9',     # tamil rupee sign
  5756.                 u'\u0E3F',     # thai currency symbol baht
  5757.                 u'\u17DB',     # khmer currency symbol riel
  5758.                 u'\u2133',     # script capital m
  5759.                 u'\u5143',     # cjk unified ideograph-5143
  5760.                 u'\u5186',     # cjk unified ideograph-5186
  5761.                 u'\u5706',     # cjk unified ideograph-5706
  5762.                 u'\u5713',     # cjk unified ideograph-5713
  5763.                 u'\uFDFC',     # rial sign
  5764.             ]
  5765.  
  5766.             # Add 20A0 (EURO-CURRENCY SIGN) to 20B5 (CEDI SIGN)
  5767.             #
  5768.             for ordChar in range(ord(u'\u20A0'), ord(u'\u20B5') + 1):
  5769.                 self._unicodeCurrencySymbols.append(unichr(ordChar))
  5770.  
  5771.         return self._unicodeCurrencySymbols
  5772.  
  5773.     def findDisplayedLabel(self, obj):
  5774.         """Return a list of the objects that are labelling this object.
  5775.  
  5776.         Argument:
  5777.         - obj: the object in question
  5778.  
  5779.         Returns a list of the objects that are labelling this object.
  5780.         """
  5781.  
  5782.         # For some reason, some objects are labelled by the same thing
  5783.         # more than once.  Go figure, but we need to check for this.
  5784.         #
  5785.         label = []
  5786.         relations = obj.getRelationSet()
  5787.         allTargets = []
  5788.  
  5789.         for relation in relations:
  5790.             if relation.getRelationType() \
  5791.                    == pyatspi.RELATION_LABELLED_BY:
  5792.  
  5793.                 # The object can be labelled by more than one thing, so we just
  5794.                 # get all the labels (from unique objects) and append them
  5795.                 # together.  An example of such objects live in the "Basic"
  5796.                 # page of the gnome-accessibility-keyboard-properties app.
  5797.                 # The "Delay" and "Speed" objects are labelled both by
  5798.                 # their names and units.
  5799.                 #
  5800.                 for i in range(0, relation.getNTargets()):
  5801.                     target = relation.getTarget(i)
  5802.                     if not target in allTargets:
  5803.                         allTargets.append(target)
  5804.                         label.append(target)
  5805.  
  5806.         # [[[TODO: HACK - we've discovered oddness in hierarchies such as
  5807.         # the gedit Edit->Preferences dialog.  In this dialog, we have
  5808.         # labeled groupings of objects.  The grouping is done via a FILLER
  5809.         # with two children - one child is the overall label, and the
  5810.         # other is the container for the grouped objects.  When we detect
  5811.         # this, we add the label to the overall context.
  5812.         #
  5813.         # We are also looking for objects which have a PANEL or a FILLER as
  5814.         # parent, and its parent has more children. Through these children,
  5815.         # a potential label with LABEL_FOR relation may exists. We want to
  5816.         # present this label.
  5817.         # This case can be seen in FileChooserDemo application, in Open dialog
  5818.         # window, the line with "Look In" label, a combobox and some
  5819.         # presentation buttons.
  5820.         #
  5821.         # Finally, we are searching the hierarchy of embedded components for
  5822.         # children that are labels.]]]
  5823.         #
  5824.         if not len(label):
  5825.             potentialLabels = []
  5826.             useLabel = False
  5827.             if (obj.getRole() == pyatspi.ROLE_EMBEDDED):
  5828.                 candidate = obj
  5829.                 while candidate.childCount:
  5830.                     candidate = candidate[0]
  5831.                 # The parent of this object may contain labels
  5832.                 # or it may contain filler that contains labels.
  5833.                 #
  5834.                 candidate = candidate.parent
  5835.                 for child in candidate:
  5836.                     if child.getRole() == pyatspi.ROLE_FILLER:
  5837.                         candidate = child
  5838.                         break
  5839.                 # If there are labels in this embedded component,
  5840.                 # they should be here.
  5841.                 #
  5842.                 for child in candidate:
  5843.                     if child.getRole() == pyatspi.ROLE_LABEL:
  5844.                         useLabel = True
  5845.                         potentialLabels.append(child)
  5846.             elif ((obj.getRole() == pyatspi.ROLE_FILLER) \
  5847.                     or (obj.getRole() == pyatspi.ROLE_PANEL)) \
  5848.                 and (obj.childCount == 2):
  5849.                 child0, child1 = obj
  5850.                 child0_role = child0.getRole()
  5851.                 child1_role = child1.getRole()
  5852.                 if child0_role == pyatspi.ROLE_LABEL \
  5853.                     and not self.__hasLabelForRelation(child0) \
  5854.                     and child1_role in [pyatspi.ROLE_FILLER, \
  5855.                                              pyatspi.ROLE_PANEL]:
  5856.                     useLabel = True
  5857.                     potentialLabels.append(child0)
  5858.                 elif child1_role == pyatspi.ROLE_LABEL \
  5859.                     and not self.__hasLabelForRelation(child1) \
  5860.                     and child0_role in [pyatspi.ROLE_FILLER, \
  5861.                                              pyatspi.ROLE_PANEL]:
  5862.                     useLabel = True
  5863.                     potentialLabels.append(child1)
  5864.             else:
  5865.                 parent = obj.parent
  5866.                 if parent and \
  5867.                     ((parent.getRole() == pyatspi.ROLE_FILLER) \
  5868.                             or (parent.getRole() == pyatspi.ROLE_PANEL)):
  5869.                     for potentialLabel in parent:
  5870.                         try:
  5871.                             useLabel = self.__isLabeling(potentialLabel, obj)
  5872.                             if useLabel:
  5873.                                 potentialLabels.append(potentialLabel)
  5874.                                 break
  5875.                         except:
  5876.                             pass
  5877.  
  5878.             if useLabel and len(potentialLabels):
  5879.                 label = potentialLabels
  5880.  
  5881.         return label
  5882.  
  5883.     def getDisplayedLabel(self, obj):
  5884.         """If there is an object labelling the given object, return the
  5885.         text being displayed for the object labelling this object.
  5886.         Otherwise, return None.
  5887.  
  5888.         Argument:
  5889.         - obj: the object in question
  5890.  
  5891.         Returns the string of the object labelling this object, or None
  5892.         if there is nothing of interest here.
  5893.         """
  5894.  
  5895.         labelString = None
  5896.         labels = self.findDisplayedLabel(obj)
  5897.         for label in labels:
  5898.             labelString = self.appendString(labelString,
  5899.                                             self.getDisplayedText(label))
  5900.  
  5901.         return labelString
  5902.  
  5903.     def __getDisplayedTextInComboBox(self, combo):
  5904.  
  5905.         """Returns the text being displayed in a combo box.  If nothing is
  5906.         displayed, then None is returned.
  5907.  
  5908.         Arguments:
  5909.         - combo: the combo box
  5910.  
  5911.         Returns the text in the combo box or an empty string if nothing is
  5912.         displayed.
  5913.         """
  5914.  
  5915.         displayedText = None
  5916.  
  5917.         # Find the text displayed in the combo box.  This is either:
  5918.         #
  5919.         # 1) The last text object that's a child of the combo box
  5920.         # 2) The selected child of the combo box.
  5921.         # 3) The contents of the text of the combo box itself when
  5922.         #    treated as a text object.
  5923.         #
  5924.         # Preference is given to #1, if it exists.
  5925.         #
  5926.         # If the label of the combo box is the same as the utterance for
  5927.         # the child object, then this utterance is only displayed once.
  5928.         #
  5929.         # [[[TODO: WDW - Combo boxes are complex beasts.  This algorithm
  5930.         # needs serious work.  Logged as bugzilla bug 319745.]]]
  5931.         #
  5932.         textObj = None
  5933.         for child in combo:
  5934.             if child and child.getRole() == pyatspi.ROLE_TEXT:
  5935.                 textObj = child
  5936.  
  5937.         if textObj:
  5938.             [displayedText, caretOffset, startOffset] = \
  5939.                 self.getTextLineAtCaret(textObj)
  5940.             #print "TEXTOBJ", displayedText
  5941.         else:
  5942.             try:
  5943.                 comboSelection = combo.querySelection()
  5944.                 selectedItem = comboSelection.getSelectedChild(0)
  5945.             except:
  5946.                 selectedItem = None
  5947.  
  5948.             if selectedItem:
  5949.                 displayedText = self.getDisplayedText(selectedItem)
  5950.                 #print "SELECTEDITEM", displayedText
  5951.             elif combo.name and len(combo.name):
  5952.                 # We give preference to the name over the text because
  5953.                 # the text for combo boxes seems to never change in
  5954.                 # some cases.  The main one where we see this is in
  5955.                 # the gaim "Join Chat" window.
  5956.                 #
  5957.                 displayedText = combo.name
  5958.                 #print "NAME", displayedText
  5959.             else:
  5960.                 [displayedText, caretOffset, startOffset] = \
  5961.                     self.getTextLineAtCaret(combo)
  5962.                 # Set to None instead of empty string.
  5963.                 displayedText = displayedText or None
  5964.                 #print "TEXT", displayedText
  5965.  
  5966.         return displayedText
  5967.  
  5968.     def getDisplayedText(self, obj):
  5969.         """Returns the text being displayed for an object.
  5970.  
  5971.         Arguments:
  5972.         - obj: the object
  5973.  
  5974.         Returns the text being displayed for an object or None if there isn't
  5975.         any text being shown.
  5976.         """
  5977.  
  5978.         displayedText = None
  5979.  
  5980.         role = obj.getRole()
  5981.         if role == pyatspi.ROLE_COMBO_BOX:
  5982.             return self.__getDisplayedTextInComboBox(obj)
  5983.  
  5984.         # The accessible text of an object is used to represent what is
  5985.         # drawn on the screen.
  5986.         #
  5987.         try:
  5988.             text = obj.queryText()
  5989.         except NotImplementedError:
  5990.             pass
  5991.         else:
  5992.             displayedText = text.getText(0, -1)
  5993.  
  5994.             # [[[WDW - HACK to account for things such as Gecko that want
  5995.             # to use the EMBEDDED_OBJECT_CHARACTER on a label to hold the
  5996.             # object that has the real accessible text for the label.  We
  5997.             # detect this by the specfic case where the text for the
  5998.             # current object is a single EMBEDDED_OBJECT_CHARACTER.  In
  5999.             # this case, we look to the child for the real text.]]]
  6000.             #
  6001.             unicodeText = displayedText.decode("UTF-8")
  6002.             if unicodeText \
  6003.                and (len(unicodeText) == 1) \
  6004.                and (unicodeText[0] == self.EMBEDDED_OBJECT_CHARACTER) \
  6005.                and obj.childCount > 0:
  6006.                 try:
  6007.                     displayedText = self.getDisplayedText(obj[0])
  6008.                 except:
  6009.                     debug.printException(debug.LEVEL_WARNING)
  6010.             elif unicodeText:
  6011.                 # [[[TODO: HACK - Welll.....we'll just plain ignore any
  6012.                 # text with EMBEDDED_OBJECT_CHARACTERs here.  We still need a
  6013.                 # general case to handle this stuff and expand objects
  6014.                 # with EMBEDDED_OBJECT_CHARACTERs.]]]
  6015.                 #
  6016.                 for i in range(0, len(unicodeText)):
  6017.                     if unicodeText[i] == self.EMBEDDED_OBJECT_CHARACTER:
  6018.                         displayedText = None
  6019.                         break
  6020.  
  6021.         if not displayedText:
  6022.             displayedText = obj.name
  6023.  
  6024.         # [[[WDW - HACK because push buttons can have labels as their
  6025.         # children.  An example of this is the Font: button on the General
  6026.         # tab in the Editing Profile dialog in gnome-terminal.
  6027.         #
  6028.         if not displayedText and role == pyatspi.ROLE_PUSH_BUTTON:
  6029.             for child in obj:
  6030.                 if child.getRole() == pyatspi.ROLE_LABEL:
  6031.                     childText = self.getDisplayedText(child)
  6032.                     if childText and len(childText):
  6033.                         displayedText = self.appendString(displayedText,
  6034.                                                           childText)
  6035.  
  6036.         return displayedText
  6037.  
  6038.     def getTextForValue(self, obj):
  6039.         """Returns the text to be displayed for the object's current value.
  6040.  
  6041.         Arguments:
  6042.         - obj: the Accessible object that may or may not have a value.
  6043.  
  6044.         Returns a string representing the value.
  6045.         """
  6046.  
  6047.         # Use ARIA "valuetext" attribute if present.  See 
  6048.         # http://bugzilla.gnome.org/show_bug.cgi?id=552965
  6049.         #
  6050.         attributes = obj.getAttributes()
  6051.         for attribute in attributes:
  6052.             if attribute.startswith("valuetext"):
  6053.                 return attribute[10:]
  6054.  
  6055.         try:
  6056.             value = obj.queryValue()
  6057.         except NotImplementedError:
  6058.             return ""
  6059.  
  6060.         # OK, this craziness is all about trying to figure out the most
  6061.         # meaningful formatting string for the floating point values.
  6062.         # The number of places to the right of the decimal point should
  6063.         # be set by the minimumIncrement, but the minimumIncrement isn't
  6064.         # always set.  So...we'll default the minimumIncrement to 1/100
  6065.         # of the range.  But, if max == min, then we'll just go for showing
  6066.         # them off to two meaningful digits.
  6067.         #
  6068.         try:
  6069.             minimumIncrement = value.minimumIncrement
  6070.         except:
  6071.             minimumIncrement = 0.0
  6072.  
  6073.         if minimumIncrement == 0.0:
  6074.             minimumIncrement = (value.maximumValue - value.minimumValue) \
  6075.                                / 100.0
  6076.  
  6077.         try:
  6078.             decimalPlaces = max(0, -math.log10(minimumIncrement))
  6079.         except:
  6080.             try:
  6081.                 decimalPlaces = max(0, -math.log10(value.minimumValue))
  6082.             except:
  6083.                 try:
  6084.                     decimalPlaces = max(0, -math.log10(value.maximumValue))
  6085.                 except:
  6086.                     decimalPlaces = 0
  6087.  
  6088.         formatter = "%%.%df" % decimalPlaces
  6089.         valueString = formatter % value.currentValue
  6090.         #minString   = formatter % value.minimumValue
  6091.         #maxString   = formatter % value.maximumValue
  6092.  
  6093.         # [[[TODO: WDW - probably want to do this as a percentage at some
  6094.         # point?  Logged as bugzilla bug 319743.]]]
  6095.         #
  6096.         return valueString
  6097.  
  6098.     def findFocusedObject(self, root):
  6099.         """Returns the accessible that has focus under or including the
  6100.         given root.
  6101.  
  6102.         TODO: This will currently traverse all children, whether they are
  6103.         visible or not and/or whether they are children of parents that
  6104.         manage their descendants.  At some point, this method should be
  6105.         optimized to take such things into account.
  6106.  
  6107.         Arguments:
  6108.         - root: the root object where to start searching
  6109.  
  6110.         Returns the object with the FOCUSED state or None if no object with
  6111.         the FOCUSED state can be found.
  6112.         """
  6113.  
  6114.         if root.getState().contains(pyatspi.STATE_FOCUSED):
  6115.             return root
  6116.  
  6117.         for child in root:
  6118.             try:
  6119.                 candidate = self.findFocusedObject(child)
  6120.                 if candidate:
  6121.                     return candidate
  6122.             except:
  6123.                 pass
  6124.  
  6125.         return None
  6126.  
  6127.     def getRealActiveDescendant(self, obj):
  6128.         """Given an object that should be a child of an object that
  6129.         manages its descendants, return the child that is the real
  6130.         active descendant carrying useful information.
  6131.  
  6132.         Arguments:
  6133.         - obj: an object that should be a child of an object that
  6134.         manages its descendants.
  6135.         """
  6136.  
  6137.         # If obj is a table cell and all of it's children are table cells
  6138.         # (probably cell renderers), then return the first child which has
  6139.         # a non zero length text string. If no such object is found, just
  6140.         # fall through and use the default approach below. See bug #376791
  6141.         # for more details.
  6142.         #
  6143.         if obj.getRole() == pyatspi.ROLE_TABLE_CELL and obj.childCount:
  6144.             nonTableCellFound = False
  6145.             for child in obj:
  6146.                 if child.getRole() != pyatspi.ROLE_TABLE_CELL:
  6147.                     nonTableCellFound = True
  6148.             if not nonTableCellFound:
  6149.                 for child in obj:
  6150.                     try:
  6151.                         text = child.queryText()
  6152.                     except NotImplementedError:
  6153.                         continue
  6154.                     else:
  6155.                         if text.getText(0, -1):
  6156.                             return child
  6157.  
  6158.         # [[[TODO: WDW - this is an odd hacky thing I've somewhat drawn
  6159.         # from Gnopernicus.  The notion here is that we get an active
  6160.         # descendant changed event, but that object tends to have children
  6161.         # itself and we need to decide what to do.  Well...the idea here
  6162.         # is that the last child (Gnopernicus chooses child(1)), tends to
  6163.         # be the child with information.  The previous children tend to
  6164.         # be non-text or just there for spacing or something.  You will
  6165.         # see this in the various table demos of gtk-demo and you will
  6166.         # also see this in the Contact Source Selector in Evolution.
  6167.         #
  6168.         # Just note that this is most likely not a really good solution
  6169.         # for the general case.  That needs more thought.  But, this
  6170.         # comment is here to remind us this is being done in poor taste
  6171.         # and we need to eventually clean up our act.]]]
  6172.         #
  6173.         if obj and obj.childCount:
  6174.             return obj[-1]
  6175.         else:
  6176.             return obj
  6177.  
  6178.     def isDesiredFocusedItem(self, obj, rolesList):
  6179.         """Called to determine if the given object and it's hierarchy of
  6180.            parent objects, each have the desired roles.
  6181.  
  6182.         Arguments:
  6183.         - obj: the accessible object to check.
  6184.         - rolesList: the list of desired roles for the components and the
  6185.           hierarchy of its parents.
  6186.  
  6187.         Returns True if all roles match.
  6188.         """
  6189.         current = obj
  6190.         for role in rolesList:
  6191.             if current is None:
  6192.                 return False
  6193.  
  6194.             if not isinstance(role, list):
  6195.                 role = [role]
  6196.  
  6197.             if isinstance(role[0], str):
  6198.                 current_role = current.getRoleName()
  6199.             else:
  6200.                 current_role = current.getRole()
  6201.  
  6202.             if not current_role in role:
  6203.                 return False
  6204.             current = current.parent
  6205.  
  6206.         return True
  6207.  
  6208.     def speakMisspeltWord(self, allTokens, badWord):
  6209.         """Called by various spell checking routine to speak the misspelt word,
  6210.            plus the context that it is being used in.
  6211.  
  6212.         Arguments:
  6213.         - allTokens: a list of all the words.
  6214.         - badWord: the misspelt word.
  6215.         """
  6216.  
  6217.         # Create an utterance to speak consisting of the misspelt
  6218.         # word plus the context where it is used (upto five words
  6219.         # to either side of it).
  6220.         #
  6221.         for i in range(0, len(allTokens)):
  6222.             if allTokens[i].startswith(badWord):
  6223.                 minIndex = i - 5
  6224.                 if minIndex < 0:
  6225.                     minIndex = 0
  6226.                 maxIndex = i + 5
  6227.                 if maxIndex > (len(allTokens) - 1):
  6228.                     maxIndex = len(allTokens) - 1
  6229.  
  6230.                 # Translators: Orca will provide more compelling output of
  6231.                 # the spell checking dialog in some applications.  The first
  6232.                 # thing it does is let them know what the misspelled word
  6233.                 # is.
  6234.                 #
  6235.                 utterances = [_("Misspelled word: %s") % badWord]
  6236.  
  6237.                 # Translators: Orca will provide more compelling output of
  6238.                 # the spell checking dialog in some applications.  The second
  6239.                 # thing it does is give the phrase containing the misspelled
  6240.                 # word in the document.  This is known as the context.
  6241.                 #
  6242.                 contextPhrase = " ".join(allTokens[minIndex:maxIndex+1])
  6243.                 utterances.append(_("Context is %s") % contextPhrase)
  6244.  
  6245.                 # Turn the list of utterances into a string.
  6246.                 text = " ".join(utterances)
  6247.                 speech.speak(text)
  6248.  
  6249.     def textLines(self, obj):
  6250.         """Creates a generator that can be used to iterate over each line
  6251.         of a text object, starting at the caret offset.
  6252.  
  6253.         Arguments:
  6254.         - obj: an Accessible that has a text specialization
  6255.  
  6256.         Returns an iterator that produces elements of the form:
  6257.         [SayAllContext, acss], where SayAllContext has the text to be
  6258.         spoken and acss is an ACSS instance for speaking the text.
  6259.         """
  6260.  
  6261.         try:
  6262.             text = obj.queryText()
  6263.         except:
  6264.             return
  6265.  
  6266.         length = text.characterCount
  6267.         offset = text.caretOffset
  6268.  
  6269.         # Determine the correct "say all by" mode to use.
  6270.         #
  6271.         if settings.sayAllStyle == settings.SAYALL_STYLE_SENTENCE:
  6272.             mode = pyatspi.TEXT_BOUNDARY_SENTENCE_END
  6273.         elif settings.sayAllStyle == settings.SAYALL_STYLE_LINE:
  6274.             mode = pyatspi.TEXT_BOUNDARY_LINE_START
  6275.         else:
  6276.             mode = pyatspi.TEXT_BOUNDARY_LINE_START
  6277.  
  6278.         # Get the next line of text to read
  6279.         #
  6280.         done = False
  6281.         while not done:
  6282.             lastEndOffset = -1
  6283.             while offset < length:
  6284.                 [lineString, startOffset, endOffset] = text.getTextAtOffset(
  6285.                     offset, mode)
  6286.  
  6287.                 # Some applications that don't support sentence boundaries
  6288.                 # will provide the line boundary results instead; others
  6289.                 # will return nothing.
  6290.                 #
  6291.                 if not lineString:
  6292.                     mode = pyatspi.TEXT_BOUNDARY_LINE_START
  6293.                     [lineString, startOffset, endOffset] = \
  6294.                         text.getTextAtOffset(offset, mode)
  6295.  
  6296.                 # [[[WDW - HACK: well...gnome-terminal sometimes wants to
  6297.                 # give us outrageous values back from getTextAtOffset
  6298.                 # (see http://bugzilla.gnome.org/show_bug.cgi?id=343133),
  6299.                 # so we try to handle it.]]]
  6300.                 #
  6301.                 if startOffset < 0:
  6302.                     break
  6303.  
  6304.                 # [[[WDW - HACK: this is here because getTextAtOffset
  6305.                 # tends not to be implemented consistently across toolkits.
  6306.                 # Sometimes it behaves properly (i.e., giving us an endOffset
  6307.                 # that is the beginning of the next line), sometimes it
  6308.                 # doesn't (e.g., giving us an endOffset that is the end of
  6309.                 # the current line).  So...we hack.  The whole 'max' deal
  6310.                 # is to account for lines that might be a brazillion lines
  6311.                 # long.]]]
  6312.                 #
  6313.                 if endOffset == lastEndOffset:
  6314.                     offset = max(offset + 1, lastEndOffset + 1)
  6315.                     lastEndOffset = endOffset
  6316.                     continue
  6317.  
  6318.                 lastEndOffset = endOffset
  6319.                 offset = endOffset
  6320.  
  6321.                 lineString = self.adjustForRepeats(lineString)
  6322.                 if lineString.isupper():
  6323.                     voice = settings.voices[settings.UPPERCASE_VOICE]
  6324.                 else:
  6325.                     voice = settings.voices[settings.DEFAULT_VOICE]
  6326.  
  6327.                 yield [speechserver.SayAllContext(obj, lineString,
  6328.                                                   startOffset, endOffset),
  6329.                        voice]
  6330.  
  6331.             moreLines = False
  6332.             relations = obj.getRelationSet()
  6333.             for relation in relations:
  6334.                 if relation.getRelationType()  \
  6335.                        == pyatspi.RELATION_FLOWS_TO:
  6336.                     obj = relation.getTarget(0)
  6337.  
  6338.                     try:
  6339.                         text = obj.queryText()
  6340.                     except NotImplementedError:
  6341.                         return
  6342.  
  6343.                     length = text.characterCount
  6344.                     offset = 0
  6345.                     moreLines = True
  6346.                     break
  6347.             if not moreLines:
  6348.                 done = True
  6349.  
  6350.     def _addRepeatSegment(self, segment, line, respectPunctuation=True):
  6351.         """Add in the latest line segment, adjusting for repeat characters
  6352.         and punctuation.
  6353.  
  6354.         Arguments:
  6355.         - segment: the segment of repeated characters.
  6356.         - line: the current built-up line to characters to speak.
  6357.         - respectPunctuation: if False, ignore punctuation level.
  6358.  
  6359.         Returns: the current built-up line plus the new segment, after
  6360.         adjusting for repeat character counts and punctuation.
  6361.         """
  6362.  
  6363.         style = settings.verbalizePunctuationStyle
  6364.         isPunctChar = True
  6365.         try:
  6366.             level, action = punctuation_settings.getPunctuationInfo(segment[0])
  6367.         except:
  6368.             isPunctChar = False
  6369.         count = len(segment)
  6370.         if (count >= settings.repeatCharacterLimit) \
  6371.            and (not segment[0] in self.whitespace):
  6372.             if (not respectPunctuation) \
  6373.                or (isPunctChar and (style <= level)):
  6374.                 repeatChar = chnames.getCharacterName(segment[0])
  6375.                 # Translators: Orca will tell you how many characters
  6376.                 # are repeated on a line of text.  For example: "22
  6377.                 # space characters".  The %d is the number and the %s
  6378.                 # is the spoken word for the character.
  6379.                 #
  6380.                 line += " " \
  6381.                      + ngettext("%(count)d %(repeatChar)s character",
  6382.                                 "%(count)d %(repeatChar)s characters",
  6383.                                 count) \
  6384.                        % {"count" : count, "repeatChar": repeatChar}
  6385.             else:
  6386.                 line += segment
  6387.         else:
  6388.             line += segment
  6389.  
  6390.         return line
  6391.  
  6392.     def adjustForLinks(self, obj, line, startOffset):
  6393.         """Adjust line to include the word "link" after any hypertext links.
  6394.  
  6395.         Arguments:
  6396.         - obj: the accessible object that this line came from.
  6397.         - line: the string to adjust for links.
  6398.         - startOffset: the caret offset at the start of the line.
  6399.  
  6400.         Returns: a new line adjusted to add the speaking of "link" after
  6401.         text which is also a link.
  6402.         """
  6403.  
  6404.         line = line.decode("UTF-8")
  6405.         endOffset = startOffset + len(line)
  6406.  
  6407.         try:
  6408.             hyperText = obj.queryHypertext()
  6409.             nLinks = hyperText.getNLinks()
  6410.         except:
  6411.             nLinks = 0
  6412.  
  6413.         adjustedLine = list(line)
  6414.         for n in range(nLinks, 0, -1):
  6415.             link = hyperText.getLink(n - 1)
  6416.  
  6417.             # We only care about links in the string, line:
  6418.             #
  6419.             if startOffset < link.endIndex < endOffset:
  6420.                 index = link.endIndex - startOffset
  6421.             elif startOffset <= link.startIndex < endOffset:
  6422.                 index = len(line) - 1
  6423.             else:
  6424.                 continue
  6425.  
  6426.             # Translators: this indicates that this piece of
  6427.             # text is a hypertext link.
  6428.             #
  6429.             linkString = " " + _("link")
  6430.  
  6431.             # If the link was not followed by a whitespace or punctuation
  6432.             # character, then add in a space to make it more presentable.
  6433.             #
  6434.             nextChar = adjustedLine[index]
  6435.             if not (nextChar in self.whitespace \
  6436.                     or punctuation_settings.getPunctuationInfo(nextChar)):
  6437.                 linkString += " "
  6438.             adjustedLine[index:index] = linkString
  6439.  
  6440.         return "".join(adjustedLine).encode("UTF-8")
  6441.  
  6442.     def adjustForRepeats(self, line):
  6443.         """Adjust line to include repeat character counts.
  6444.         As some people will want this and others might not,
  6445.         there is a setting in settings.py that determines
  6446.         whether this functionality is enabled.
  6447.  
  6448.         repeatCharacterLimit = <n>
  6449.  
  6450.         If <n> is 0, then there would be no repeat characters.
  6451.         Otherwise <n> would be the number of same characters (or more)
  6452.         in a row that cause the repeat character count output.
  6453.         If the value is set to 1, 2 or 3 then it's treated as if it was
  6454.         zero. In other words, no repeat character count is given.
  6455.  
  6456.         Arguments:
  6457.         - line: the string to adjust for repeat character counts.
  6458.  
  6459.         Returns: a new line adjusted for repeat character counts (if enabled).
  6460.         """
  6461.  
  6462.         line = line.decode("UTF-8")
  6463.  
  6464.         if (len(line) < 4) or (settings.repeatCharacterLimit < 4):
  6465.             return line.encode("UTF-8")
  6466.  
  6467.         newLine = u''
  6468.         segment = lastChar = line[0]
  6469.  
  6470.         multipleChars = False
  6471.         for i in range(1, len(line)):
  6472.             if line[i] == lastChar:
  6473.                 segment += line[i]
  6474.             else:
  6475.                 multipleChars = True
  6476.                 newLine = self._addRepeatSegment(segment, newLine)
  6477.                 segment = line[i]
  6478.  
  6479.             lastChar = line[i]
  6480.  
  6481.         newLine = self._addRepeatSegment(segment, newLine, multipleChars)
  6482.  
  6483.         # Pylint is confused and flags this with the following error:
  6484.         #
  6485.         # E1103:5188:Script.adjustForRepeats: Instance of 'True' has
  6486.         # no 'encode' member (but some types could not be inferred)
  6487.         #
  6488.         # We know newLine is a unicode string, so we'll just tell pylint
  6489.         # that we know what we are doing.
  6490.         #
  6491.         # pylint: disable-msg=E1103
  6492.  
  6493.         return newLine.encode("UTF-8")
  6494.  
  6495.     def _getPronunciationForSegment(self, segment):
  6496.         """Adjust the word segment to potentially replace it with what
  6497.         those words actually sound like. Two pronunciation dictionaries
  6498.         are checked. First the application specific one (which might be
  6499.         empty), then the default (global) one.
  6500.  
  6501.         Arguments:
  6502.         - segment: the string to adjust for words in the pronunciation
  6503.           dictionaries.
  6504.  
  6505.         Returns: a new word segment adjusted for words found in the
  6506.         pronunciation dictionaries, or the original word segment if there
  6507.         was no dictionary entry.
  6508.         """
  6509.  
  6510.         newSegment = pronunciation_dict.getPronunciation(segment,
  6511.                                      self.app_pronunciation_dict)
  6512.         if newSegment == segment:
  6513.             newSegment = pronunciation_dict.getPronunciation(segment)
  6514.  
  6515.         return newSegment
  6516.  
  6517.     def adjustForPronunciation(self, line):
  6518.         """Adjust the line to replace words in the pronunciation dictionary,
  6519.         with what those words actually sound like.
  6520.  
  6521.         Arguments:
  6522.         - line: the string to adjust for words in the pronunciation dictionary.
  6523.  
  6524.         Returns: a new line adjusted for words found in the pronunciation
  6525.         dictionary.
  6526.         """
  6527.  
  6528.         line = line.decode("UTF-8")
  6529.         newLine = segment = u''
  6530.  
  6531.         for i in range(0, len(line)):
  6532.             if self.isWordDelimiter(line[i]):
  6533.                 if len(segment) != 0:
  6534.                     newLine = newLine + \
  6535.                               self._getPronunciationForSegment(segment)
  6536.                 newLine = newLine + line[i]
  6537.                 segment = u''
  6538.             else:
  6539.                 segment += line[i]
  6540.  
  6541.         if len(segment) != 0:
  6542.             newLine = newLine + self._getPronunciationForSegment(segment)
  6543.  
  6544.         return newLine.encode("UTF-8")
  6545.  
  6546.     def getLinkIndex(self, obj, characterIndex):
  6547.         """A brute force method to see if an offset is a link.  This
  6548.         is provided because not all Accessible Hypertext implementations
  6549.         properly support the getLinkIndex method.  Returns an index of
  6550.         0 or greater of the characterIndex is on a hyperlink.
  6551.  
  6552.         Arguments:
  6553.         -obj: the Accessible object with the Accessible Hypertext specialization
  6554.         -characterIndex: the text position to check
  6555.         """
  6556.  
  6557.         if not obj:
  6558.             return -1
  6559.  
  6560.         try:
  6561.             obj.queryText()
  6562.         except NotImplementedError:
  6563.             return -1
  6564.  
  6565.         try:
  6566.             hypertext = obj.queryHypertext()
  6567.         except NotImplementedError:
  6568.             return -1
  6569.  
  6570.         for i in xrange(hypertext.getNLinks()):
  6571.             link = hypertext.getLink(i)
  6572.             if (characterIndex >= link.startIndex) \
  6573.                and (characterIndex <= link.endIndex):
  6574.                 return i
  6575.  
  6576.         return -1
  6577.  
  6578.     def getCellIndex(self, obj):
  6579.         """Returns the index of the cell which should be used with the
  6580.         table interface.  This is necessary because in some apps we
  6581.         cannot count on getIndexInParent() returning the index we need.
  6582.  
  6583.         Arguments:
  6584.         -obj: the table cell whose index we need.
  6585.         """
  6586.         return obj.getIndexInParent()
  6587.  
  6588.     def isSentenceDelimiter(self, currentChar, previousChar):
  6589.         """Returns True if we are positioned at the end of a sentence.
  6590.         This is determined by checking if the current character is a 
  6591.         white space character and the previous character is one of the 
  6592.         normal end-of-sentence punctuation characters.
  6593.  
  6594.         Arguments:
  6595.         - currentChar:  the current character
  6596.         - previousChar: the previous character
  6597.  
  6598.         Returns True if the given character is a sentence delimiter.
  6599.         """
  6600.  
  6601.         if not isinstance(currentChar, unicode):
  6602.             currentChar = currentChar.decode("UTF-8")
  6603.  
  6604.         if not isinstance(previousChar, unicode):
  6605.             previousChar = previousChar.decode("UTF-8")
  6606.  
  6607.         if currentChar == '\r' or currentChar == '\n':
  6608.             return True
  6609.  
  6610.         return (currentChar in self.whitespace and previousChar in '!.?:;')
  6611.  
  6612.     def isWordDelimiter(self, character):
  6613.         """Returns True if the given character is a word delimiter.
  6614.  
  6615.         Arguments:
  6616.         - character: the character in question
  6617.  
  6618.         Returns True if the given character is a word delimiter.
  6619.         """
  6620.  
  6621.         if not isinstance(character, unicode):
  6622.             character = character.decode("UTF-8")
  6623.  
  6624.         return (character in self.whitespace) \
  6625.                or (character in '!*+,-./:;<=>?@[\]^_{|}') \
  6626.                or (character == self.NO_BREAK_SPACE_CHARACTER)
  6627.  
  6628.     def getFrame(self, obj):
  6629.         """Returns the frame containing this object, or None if this object
  6630.         is not inside a frame.
  6631.  
  6632.         Arguments:
  6633.         - obj: the Accessible object
  6634.         """
  6635.  
  6636.         debug.println(debug.LEVEL_FINEST,
  6637.                       "Finding frame for source.name="
  6638.                       + obj.name or "None")
  6639.  
  6640.         while obj \
  6641.               and (obj != obj.parent) \
  6642.               and (obj.getRole() != pyatspi.ROLE_FRAME):
  6643.             obj = obj.parent
  6644.             if obj:
  6645.                 debug.println(debug.LEVEL_FINEST, "--> obj.name="
  6646.                           + obj.name or "None")
  6647.  
  6648.         if obj and (obj.getRole() == pyatspi.ROLE_FRAME):
  6649.             pass
  6650.         else:
  6651.             obj = None
  6652.  
  6653.         return obj
  6654.  
  6655.     def getTopLevel(self, obj):
  6656.         """Returns the top-level object (frame, dialog ...) containing this
  6657.         object, or None if this object is not inside a top-level object.
  6658.  
  6659.         Arguments:
  6660.         - obj: the Accessible object
  6661.         """
  6662.  
  6663.         debug.println(debug.LEVEL_FINEST,
  6664.                       "Finding top-level object for source.name="
  6665.                       + obj.name or "None")
  6666.  
  6667.         while obj \
  6668.               and obj.parent \
  6669.               and (obj != obj.parent) \
  6670.               and (obj.parent.getRole() != pyatspi.ROLE_APPLICATION):
  6671.             obj = obj.parent
  6672.             debug.println(debug.LEVEL_FINEST, "--> obj.name="
  6673.                           + obj.name or "None")
  6674.  
  6675.         if obj and obj.parent and \
  6676.            (obj.parent.getRole() == pyatspi.ROLE_APPLICATION):
  6677.             pass
  6678.         else:
  6679.             obj = None
  6680.  
  6681.         return obj
  6682.  
  6683.     def getTopLevelName(self, obj):
  6684.         """ Returns the name of the top-level object. See getTopLevel.
  6685.         """
  6686.         top = self.getTopLevel(obj)
  6687.         if (not top) or (not top.name):
  6688.             return ""
  6689.         else:
  6690.             return top.name
  6691.  
  6692.     def getTextLineAtCaret(self, obj, offset=None):
  6693.         """Gets the line of text where the caret is.
  6694.  
  6695.         Argument:
  6696.         - obj: an Accessible object that implements the AccessibleText
  6697.           interface
  6698.         - offset: an optional caret offset to use. (Not used here at the
  6699.           moment, but needed in the Gecko script)
  6700.  
  6701.         Returns the [string, caretOffset, startOffset] for the line of text
  6702.         where the caret is.
  6703.         """
  6704.  
  6705.         # Get the the AccessibleText interrface
  6706.         #
  6707.         try:
  6708.             text = obj.queryText()
  6709.         except NotImplementedError:
  6710.             return ["", 0, 0]
  6711.  
  6712.         # The caret might be positioned at the very end of the text area.
  6713.         # In these cases, calling text.getTextAtOffset on an offset that's
  6714.         # not positioned to a character can yield unexpected results.  In
  6715.         # particular, we'll see the Gecko toolkit return a start and end
  6716.         # offset of (0, 0), and we'll see other implementations, such as
  6717.         # gedit, return reasonable results (i.e., gedit will give us the
  6718.         # last line).
  6719.         #
  6720.         # In order to accommodate the differing behavior of different
  6721.         # AT-SPI implementations, we'll make sure we give getTextAtOffset
  6722.         # the offset of an actual character.  Then, we'll do a little check
  6723.         # to see if that character is a newline - if it is, we'll treat it
  6724.         # as the line.
  6725.         #
  6726.         if text.caretOffset == text.characterCount:
  6727.             caretOffset = max(0, text.caretOffset - 1)
  6728.             character = text.getText(caretOffset,
  6729.                                      caretOffset + 1).decode("UTF-8")
  6730.         else:
  6731.             caretOffset = text.caretOffset
  6732.             character = None
  6733.  
  6734.         if (text.caretOffset == text.characterCount) \
  6735.             and (character == "\n"):
  6736.             content = ""
  6737.             startOffset = caretOffset
  6738.         else:
  6739.             # Get the line containing the caret.  [[[TODO: HACK WDW - If
  6740.             # there's only 1 character in the string, well, we get it.  We
  6741.             # do this because Gecko's implementation of getTextAtOffset
  6742.             # is broken if there is just one character in the string.]]]
  6743.             #
  6744.             if (text.characterCount == 1):
  6745.                 lineString = text.getText(caretOffset, caretOffset + 1)
  6746.                 startOffset = caretOffset
  6747.             else:
  6748.                 [lineString, startOffset, endOffset] = text.getTextAtOffset(
  6749.                     caretOffset, pyatspi.TEXT_BOUNDARY_LINE_START)
  6750.  
  6751.             # Sometimes we get the trailing line-feed-- remove it
  6752.             #
  6753.             content = lineString.decode("UTF-8")
  6754.             if content[-1:] == "\n":
  6755.                 content = content[:-1]
  6756.  
  6757.         return [content.encode("UTF-8"), text.caretOffset, startOffset]
  6758.  
  6759.     def getNodeLevel(self, obj):
  6760.         """Determines the node level of this object if it is in a tree
  6761.         relation, with 0 being the top level node.  If this object is
  6762.         not in a tree relation, then -1 will be returned.
  6763.  
  6764.         Arguments:
  6765.         -obj: the Accessible object
  6766.         """
  6767.  
  6768.         if not obj:
  6769.             return -1
  6770.  
  6771.         nodes = []
  6772.         node = obj
  6773.         done = False
  6774.         while not done:
  6775.             relations = node.getRelationSet()
  6776.             node = None
  6777.             for relation in relations:
  6778.                 if relation.getRelationType() \
  6779.                        == pyatspi.RELATION_NODE_CHILD_OF:
  6780.                     node = relation.getTarget(0)
  6781.                     break
  6782.  
  6783.             # We want to avoid situations where something gives us an
  6784.             # infinite cycle of nodes.  Bon Echo has been seen to do
  6785.             # this (see bug 351847).
  6786.             #
  6787.             if (len(nodes) > 100) or nodes.count(node):
  6788.                 debug.println(debug.LEVEL_WARNING,
  6789.                               "Script.getNodeLevel detected a cycle!!!")
  6790.                 done = True
  6791.             elif node:
  6792.                 nodes.append(node)
  6793.                 debug.println(debug.LEVEL_FINEST,
  6794.                               "Script.getNodeLevel %d" % len(nodes))
  6795.             else:
  6796.                 done = True
  6797.  
  6798.         return len(nodes) - 1
  6799.  
  6800.     def getChildNodes(self, obj):
  6801.         """Gets all of the children that have RELATION_NODE_CHILD_OF pointing
  6802.         to this expanded table cell.
  6803.  
  6804.         Arguments:
  6805.         -obj: the Accessible Object
  6806.  
  6807.         Returns: a list of all the child nodes
  6808.         """
  6809.  
  6810.         try:
  6811.             table = obj.parent.queryTable()
  6812.         except:
  6813.             return []
  6814.         else:
  6815.             if not obj.getState().contains(pyatspi.STATE_EXPANDED):
  6816.                 return []
  6817.  
  6818.         nodes = []
  6819.         index = self.getCellIndex(obj)
  6820.         row = table.getRowAtIndex(index)
  6821.         col = table.getColumnAtIndex(index)
  6822.         nodeLevel = self.getNodeLevel(obj)
  6823.         done = False
  6824.  
  6825.         # Candidates will be in the rows beneath the current row.
  6826.         # Only check in the current column and stop checking as
  6827.         # soon as the node level of a candidate is equal or less
  6828.         # than our current level.
  6829.         #
  6830.         for i in range(row+1, table.nRows):
  6831.             cell = table.getAccessibleAt(i, col)
  6832.             relations = cell.getRelationSet()
  6833.             for relation in relations:
  6834.                 if relation.getRelationType() \
  6835.                        == pyatspi.RELATION_NODE_CHILD_OF:
  6836.                     nodeOf = relation.getTarget(0)
  6837.                     if self.isSameObject(obj, nodeOf):
  6838.                         nodes.append(cell)
  6839.                     else:
  6840.                         currentLevel = self.getNodeLevel(nodeOf)
  6841.                         if currentLevel <= nodeLevel:
  6842.                             done = True
  6843.                     break
  6844.             if done:
  6845.                 break
  6846.  
  6847.         return nodes
  6848.  
  6849.     def getKeyBinding(self, obj):
  6850.         """Gets the mnemonic, accelerator string and possibly shortcut
  6851.         for the given object.  These are based upon the first accessible
  6852.         action for the object.
  6853.  
  6854.         Arguments:
  6855.         - obj: the Accessible object
  6856.  
  6857.         Returns: list containing strings: [mnemonic, shortcut, accelerator]
  6858.         """
  6859.  
  6860.         try:
  6861.             action = obj.queryAction()
  6862.         except NotImplementedError:
  6863.             return ["", "", ""]
  6864.  
  6865.         # Action is a string in the format, where the mnemonic and/or
  6866.         # accelerator can be missing.
  6867.         #
  6868.         # <mnemonic>;<full-path>;<accelerator>
  6869.         #
  6870.         # The keybindings in <full-path> should be separated by ":"
  6871.         #
  6872.         bindingStrings = action.getKeyBinding(0).decode("UTF-8").split(';')
  6873.  
  6874.         if len(bindingStrings) == 3:
  6875.             mnemonic       = bindingStrings[0]
  6876.             fullShortcut   = bindingStrings[1]
  6877.             accelerator    = bindingStrings[2]
  6878.         elif len(bindingStrings) > 0:
  6879.             mnemonic       = ""
  6880.             fullShortcut   = bindingStrings[0]
  6881.             try:
  6882.                 accelerator = bindingStrings[1]
  6883.             except:
  6884.                 accelerator = ""
  6885.         else:
  6886.             mnemonic       = ""
  6887.             fullShortcut   = ""
  6888.             accelerator    = ""
  6889.  
  6890.         fullShortcut = fullShortcut.replace("<","")
  6891.         fullShortcut = fullShortcut.replace(">"," ")
  6892.         fullShortcut = fullShortcut.replace(":"," ").strip()
  6893.  
  6894.         # If the accelerator or mnemonic strings includes a Space,
  6895.         # make sure we speak it.
  6896.         #
  6897.         if mnemonic.endswith(" "):
  6898.             # Translators: this is the spoken word for the space character
  6899.             #
  6900.             mnemonic += _("space")
  6901.         mnemonic = mnemonic.replace("<","")
  6902.         mnemonic = mnemonic.replace(">"," ").strip()
  6903.  
  6904.         if accelerator.endswith(" "):
  6905.             # Translators: this is the spoken word for the space character
  6906.             #
  6907.             accelerator += _("space")
  6908.         accelerator = accelerator.replace("<","")
  6909.         accelerator = accelerator.replace(">"," ").strip()
  6910.  
  6911.         debug.println(debug.LEVEL_FINEST, "default.getKeyBinding: " \
  6912.                       + repr([mnemonic, fullShortcut, accelerator]))
  6913.  
  6914.         return [mnemonic, fullShortcut, accelerator]
  6915.  
  6916.     def getKnownApplications(self):
  6917.         """Retrieves the list of currently running apps for the desktop
  6918.         as a list of Accessible objects.
  6919.         """
  6920.  
  6921.         debug.println(debug.LEVEL_FINEST,
  6922.                       "Script.getKnownApplications...")
  6923.  
  6924.         apps = filter(lambda x: x is not None,
  6925.                       pyatspi.Registry.getDesktop(0))
  6926.  
  6927.         debug.println(debug.LEVEL_FINEST,
  6928.                       "...Script.getKnownApplications")
  6929.  
  6930.         return apps
  6931.  
  6932.     def getObjects(self, root, onlyShowing=True):
  6933.         """Returns a list of all objects under the given root.  Objects
  6934.         are returned in no particular order - this function does a simple
  6935.         tree traversal, ignoring the children of objects which report the
  6936.         MANAGES_DESCENDANTS state.
  6937.  
  6938.         Arguments:
  6939.         - root:        the Accessible object to traverse
  6940.         - onlyShowing: examine only those objects that are SHOWING
  6941.  
  6942.         Returns: a list of all objects under the specified object
  6943.         """
  6944.  
  6945.         # The list of object we'll return
  6946.         #
  6947.         objlist = []
  6948.  
  6949.         # Start at the first child of the given object
  6950.         #
  6951.         if root.childCount <= 0:
  6952.             return objlist
  6953.  
  6954.         for i, child in enumerate(root):
  6955.             debug.println(debug.LEVEL_FINEST,
  6956.                           "Script.getObjects looking at child %d" % i)
  6957.             if child \
  6958.                and ((not onlyShowing) or (onlyShowing and \
  6959.                     (child.getState().contains(pyatspi.STATE_SHOWING)))):
  6960.                 objlist.append(child)
  6961.                 if (child.getState().contains( \
  6962.                     pyatspi.STATE_MANAGES_DESCENDANTS) == 0) \
  6963.                     and (child.childCount > 0):
  6964.                     objlist.extend(self.getObjects(child, onlyShowing))
  6965.  
  6966.         return objlist
  6967.  
  6968.     def findByRole(self, root, role, onlyShowing=True):
  6969.         """Returns a list of all objects of a specific role beneath the
  6970.         given root.  [[[TODO: MM - This is very inefficient - this should
  6971.         do it's own traversal and not add objects to the list that aren't
  6972.         of the specified role.  Instead it uses the traversal from
  6973.         getObjects and then deletes objects from the list that aren't of
  6974.         the specified role.  Logged as bugzilla bug 319740.]]]
  6975.  
  6976.         Arguments:
  6977.         - root the Accessible object to traverse
  6978.         - role the string describing the Accessible role of the object
  6979.         - onlyShowing: examine only those objects that are SHOWING
  6980.  
  6981.         Returns a list of descendants of the root that are of the given role.
  6982.         """
  6983.  
  6984.         objlist = []
  6985.         allobjs = self.getObjects(root, onlyShowing)
  6986.         for o in allobjs:
  6987.             if o.getRole() == role:
  6988.                 objlist.append(o)
  6989.         return objlist
  6990.  
  6991.     def findUnrelatedLabels(self, root):
  6992.         """Returns a list containing all the unrelated (i.e., have no
  6993.         relations to anything and are not a fundamental element of a
  6994.         more atomic component like a combo box) labels under the given
  6995.         root.  Note that the labels must also be showing on the display.
  6996.  
  6997.         Arguments:
  6998.         - root the Accessible object to traverse
  6999.  
  7000.         Returns a list of unrelated labels under the given root.
  7001.         """
  7002.  
  7003.         # Find all the labels in the dialog
  7004.         #
  7005.         allLabels = self.findByRole(root, pyatspi.ROLE_LABEL)
  7006.  
  7007.         # add the names of only those labels which are not associated with
  7008.         # other objects (i.e., empty relation sets).
  7009.         #
  7010.         # [[[WDW - HACK: In addition, do not grab free labels whose
  7011.         # parents are push buttons because push buttons can have labels as
  7012.         # children.]]]
  7013.         #
  7014.         # [[[WDW - HACK: panels with labelled borders will have a child
  7015.         # label that does not have its relation set.  So...we check to see
  7016.         # if the panel's name is the same as the label's name.  If so, we
  7017.         # ignore the label.]]]
  7018.         #
  7019.         unrelatedLabels = []
  7020.  
  7021.         for label in allLabels:
  7022.             relations = label.getRelationSet()
  7023.             if len(relations) == 0:
  7024.                 parent = label.parent
  7025.                 if parent and (parent.getRole() == pyatspi.ROLE_PUSH_BUTTON):
  7026.                     pass
  7027.                 elif parent and (parent.getRole() == pyatspi.ROLE_PANEL) \
  7028.                    and (parent.name == label.name):
  7029.                     pass
  7030.                 elif label.getState().contains(pyatspi.STATE_SHOWING):
  7031.                     unrelatedLabels.append(label)
  7032.  
  7033.         # Now sort the labels based on their geographic position, top to
  7034.         # bottom, left to right.  This is a very inefficient sort, but the
  7035.         # assumption here is that there will not be a lot of labels to
  7036.         # worry about.
  7037.         #
  7038.         sortedLabels = []
  7039.         for label in unrelatedLabels:
  7040.             label_extents = \
  7041.                 label.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
  7042.             index = 0
  7043.             for sortedLabel in sortedLabels:
  7044.                 sorted_extents = \
  7045.                     sortedLabel.queryComponent().getExtents(
  7046.                   pyatspi.DESKTOP_COORDS)
  7047.                 if (label_extents.y > sorted_extents.y) \
  7048.                    or ((label_extents.y == sorted_extents.y) \
  7049.                        and (label_extents.x > sorted_extents.x)):
  7050.                     index += 1
  7051.                 else:
  7052.                     break
  7053.             sortedLabels.insert(index, label)
  7054.  
  7055.         return sortedLabels
  7056.  
  7057.     def phoneticSpellCurrentItem(self, itemString):
  7058.         """Phonetically spell the current flat review word or line.
  7059.  
  7060.         Arguments:
  7061.         - itemString: the string to phonetically spell.
  7062.         """
  7063.  
  7064.         for (charIndex, character) in enumerate(itemString.decode("UTF-8")):
  7065.             if character.isupper():
  7066.                 voice = settings.voices[settings.UPPERCASE_VOICE]
  7067.                 character = character.lower()
  7068.             else:
  7069.                 voice =  settings.voices[settings.DEFAULT_VOICE]
  7070.             phoneticString = phonnames.getPhoneticName(character)
  7071.             speech.speak(phoneticString, voice)
  7072.  
  7073.     def printAncestry(self, child):
  7074.         """Prints a hierarchical view of a child's ancestry."""
  7075.  
  7076.         if not child:
  7077.             return
  7078.  
  7079.         ancestorList = [child]
  7080.         parent = child.parent
  7081.         while parent and (parent.parent != parent):
  7082.             ancestorList.insert(0, parent)
  7083.             parent = parent.parent
  7084.  
  7085.         indent = ""
  7086.         for ancestor in ancestorList:
  7087.             print indent + "+-", debug.getAccessibleDetails(ancestor)
  7088.             indent += "  "
  7089.  
  7090.     def printHierarchy(self, root, ooi, indent="",
  7091.                        onlyShowing=True, omitManaged=True):
  7092.         """Prints the accessible hierarchy of all children
  7093.  
  7094.         Arguments:
  7095.         -indent:      Indentation string
  7096.         -root:        Accessible where to start
  7097.         -ooi:         Accessible object of interest
  7098.         -onlyShowing: If True, only show children painted on the screen
  7099.         -omitManaged: If True, omit children that are managed descendants
  7100.         """
  7101.  
  7102.         if not root:
  7103.             return
  7104.  
  7105.         if root == ooi:
  7106.             print indent + "(*)", debug.getAccessibleDetails(root)
  7107.         else:
  7108.             print indent + "+-", debug.getAccessibleDetails(root)
  7109.  
  7110.         rootManagesDescendants = root.getState().contains( \
  7111.                                       pyatspi.STATE_MANAGES_DESCENDANTS)
  7112.  
  7113.         for child in root:
  7114.             if child == root:
  7115.                 print indent + "  " + "WARNING CHILD == PARENT!!!"
  7116.             elif not child:
  7117.                 print indent + "  " + "WARNING CHILD IS NONE!!!"
  7118.             elif child.parent != root:
  7119.                 print indent + "  " + "WARNING CHILD.PARENT != PARENT!!!"
  7120.             else:
  7121.                 paint = (not onlyShowing) or (onlyShowing and \
  7122.                          child.getState().contains(pyatspi.STATE_SHOWING))
  7123.                 paint = paint \
  7124.                         and ((not omitManaged) \
  7125.                              or (omitManaged and not rootManagesDescendants))
  7126.  
  7127.                 if paint:
  7128.                     self.printHierarchy(child,
  7129.                                         ooi,
  7130.                                         indent + "  ",
  7131.                                         onlyShowing,
  7132.                                         omitManaged)
  7133.  
  7134.     def printApps(self):
  7135.         """Prints a list of all applications to stdout."""
  7136.  
  7137.         level = debug.LEVEL_OFF
  7138.  
  7139.         apps = self.getKnownApplications()
  7140.         debug.println(level, "There are %d Accessible applications" % len(apps))
  7141.         for app in apps:
  7142.             debug.printDetails(level, "  App: ", app, False)
  7143.             for child in app:
  7144.                 debug.printDetails(level, "    Window: ", child, False)
  7145.                 if child.parent != app:
  7146.                     debug.println(level,
  7147.                                   "      WARNING: child's parent is not app!!!")
  7148.  
  7149.         return True
  7150.  
  7151.     def printActiveApp(self):
  7152.         """Prints the active application."""
  7153.  
  7154.         level = debug.LEVEL_OFF
  7155.  
  7156.         window = self.findActiveWindow()
  7157.         if not window:
  7158.             debug.println(level, "Active application: None")
  7159.         else:
  7160.             app = window.getApplication()
  7161.             if not app:
  7162.                 debug.println(level, "Active application: None")
  7163.             else:
  7164.                 debug.println(level, "Active application: %s" % app.name)
  7165.  
  7166.     def isInActiveApp(self, obj):
  7167.         """Returns True if the given object is from the same application that
  7168.         currently has keyboard focus.
  7169.  
  7170.         Arguments:
  7171.         - obj: an Accessible object
  7172.         """
  7173.  
  7174.         if not obj:
  7175.             return False
  7176.         else:
  7177.             return orca_state.locusOfFocus \
  7178.                    and (orca_state.locusOfFocus.getApplication() \
  7179.                           == obj.getApplication())
  7180.  
  7181.     def findActiveWindow(self):
  7182.         """Traverses the list of known apps looking for one who has an
  7183.         immediate child (i.e., a window) whose state includes the active state.
  7184.  
  7185.         Returns the Python Accessible of the window that's active or None if
  7186.         no windows are active.
  7187.         """
  7188.  
  7189.         window = None
  7190.         apps = self.getKnownApplications()
  7191.         for app in apps:
  7192.             for child in app:
  7193.                 try:
  7194.                     state = child.getState()
  7195.                     if state.contains(pyatspi.STATE_ACTIVE):
  7196.                         window = child
  7197.                         break
  7198.                 except:
  7199.                     debug.printException(debug.LEVEL_FINEST)
  7200.  
  7201.         return window
  7202.  
  7203.     def getAncestor(self, obj, ancestorRoles, stopRoles):
  7204.         """Returns the object of the specified roles which contains the
  7205.         given object, or None if the given object is not contained within
  7206.         an object the specified roles.
  7207.  
  7208.         Arguments:
  7209.         - obj: the Accessible object
  7210.         - ancestorRoles: the list of roles to look for
  7211.         - stopRoles: the list of roles to stop the search at
  7212.         """
  7213.  
  7214.         if not obj:
  7215.             return None
  7216.  
  7217.         if not isinstance(ancestorRoles, [].__class__):
  7218.             ancestorRoles = [ancestorRoles]
  7219.  
  7220.         if not isinstance(stopRoles, [].__class__):
  7221.             stopRoles = [stopRoles]
  7222.  
  7223.         ancestor = None
  7224.  
  7225.         obj = obj.parent
  7226.         while obj and (obj != obj.parent):
  7227.             if obj.getRole() in ancestorRoles:
  7228.                 ancestor = obj
  7229.                 break
  7230.             elif obj.getRole() in stopRoles:
  7231.                 break
  7232.             else:
  7233.                 obj = obj.parent
  7234.  
  7235.         return ancestor
  7236.  
  7237.     def saveOldAppSettings(self):
  7238.         """Save a copy of all the existing application specific settings
  7239.         (as specified by the settings.userCustomizableSettings dictionary)."""
  7240.  
  7241.         return orca_prefs.readPreferences()
  7242.  
  7243.     def restoreOldAppSettings(self, prefsDict):
  7244.         """Restore a copy of all the previous saved application settings.
  7245.  
  7246.         Arguments:
  7247.         - prefsDict: the dictionary containing the old application settings.
  7248.         """
  7249.  
  7250.         for key in settings.userCustomizableSettings:
  7251.             if key in prefsDict:
  7252.                 setattr(settings, key, prefsDict[key])
  7253.  
  7254.     ########################################################################
  7255.     #                                                                      #
  7256.     # METHODS FOR DRAWING RECTANGLES AROUND OBJECTS ON THE SCREEN          #
  7257.     #                                                                      #
  7258.     ########################################################################
  7259.  
  7260.     def drawOutline(self, x, y, width, height):
  7261.         """Draws an outline around the accessible, erasing the
  7262.         last drawn outline in the process."""
  7263.  
  7264.         if (x == -1) and (y == 0) and (width == 0) and (height == 0):
  7265.             outline.erase()
  7266.         else:
  7267.             outline.draw(x, y, width, height)
  7268.  
  7269.     def outlineAccessible(self, accessible):
  7270.         """Draws a rectangular outline around the accessible, erasing the
  7271.         last drawn rectangle in the process."""
  7272.  
  7273.         try:
  7274.             component = accessible.queryComponent()
  7275.         except AttributeError:
  7276.             self.drawOutline(-1, 0, 0, 0)
  7277.         except NotImplementedError:
  7278.             pass
  7279.         else:
  7280.             visibleRectangle = \
  7281.                 component.getExtents(pyatspi.DESKTOP_COORDS)
  7282.             self.drawOutline(visibleRectangle.x, visibleRectangle.y,
  7283.                              visibleRectangle.width, visibleRectangle.height)
  7284.  
  7285.     def isTextSelected(self, obj, startOffset, endOffset):
  7286.         """Returns an indication of whether the text is selected by
  7287.         comparing the text offset with the various selected regions of
  7288.         text for this accessible object.
  7289.  
  7290.         Arguments:
  7291.         - obj: the Accessible object.
  7292.         - startOffset: text start offset.
  7293.         - endOffset: text end offset.
  7294.  
  7295.         Returns an indication of whether the text is selected.
  7296.         """
  7297.  
  7298.         # If start offset and end offset are the same, just return False.
  7299.         # This is possible if there was no text spoken in onCaretMoved.
  7300.         #
  7301.         if startOffset == endOffset:
  7302.             return False
  7303.  
  7304.         try:
  7305.             text = obj.queryText()
  7306.         except:
  7307.             return False
  7308.  
  7309.         for i in xrange(text.getNSelections()):
  7310.             [startSelOffset, endSelOffset] = text.getSelection(i)
  7311.             if (startOffset >= startSelOffset) and (endOffset <= endSelOffset):
  7312.                 return True
  7313.  
  7314.         return False
  7315.  
  7316.     def _saveSpokenTextRange(self, startOffset, endOffset):
  7317.         """Save away the start and end offset of the range of text that
  7318.         was spoken. It will be used by speakTextSelectionState, to try
  7319.         to determine if the text was selected or unselected.
  7320.  
  7321.         Arguments:
  7322.         - startOffset: the start of the spoken text range.
  7323.         - endOffset: the end of the spoken text range.
  7324.         """
  7325.  
  7326.         self.pointOfReference["spokenTextRange"] = [startOffset, endOffset]
  7327.  
  7328.     def _saveLastCursorPosition(self, obj, caretOffset):
  7329.         """Save away the current text cursor position for next time.
  7330.  
  7331.         Arguments:
  7332.         - obj: the current accessible
  7333.         - caretOffset: the cursor position within this object
  7334.         """
  7335.  
  7336.         self.pointOfReference["lastCursorPosition"] = [obj, caretOffset]
  7337.  
  7338.     def _saveLastTextSelections(self, text):
  7339.         """Save away the list of text selections for next time.
  7340.  
  7341.         Arguments:
  7342.         - text: the text object.
  7343.         """
  7344.  
  7345.         self.pointOfReference["lastSelections"] = []
  7346.         for i in xrange(text.getNSelections()):
  7347.             self.pointOfReference["lastSelections"].append(
  7348.               text.getSelection(i))
  7349.  
  7350.     def speakTextSelectionState(self, obj, startOffset, endOffset):
  7351.         """Speak "selected" if the text was just selected, "unselected" if
  7352.         it was just unselected.
  7353.  
  7354.         Arguments:
  7355.         - obj: the Accessible object.
  7356.         - startOffset: text start offset.
  7357.         - endOffset: text end offset.
  7358.         """
  7359.  
  7360.         try:
  7361.             text = obj.queryText()
  7362.         except:
  7363.             return
  7364.  
  7365.         # Handle special cases.
  7366.         #
  7367.         # Control-Shift-Page_Down:
  7368.         #          speak "line selected to end from previous cursor position".
  7369.         # Control-Shift-Page_Up:
  7370.         #        speak "line selected from start to previous cursor position".
  7371.         #
  7372.         # Shift-Page_Down:    speak "page <state> from cursor position".
  7373.         # Shift-Page_Up:      speak "page <state> to cursor position".
  7374.         #
  7375.         # Control-Shift-Down: speak "line <state> down from cursor position".
  7376.         # Control-Shift-Up:   speak "line <state> up from cursor position".
  7377.         #
  7378.         # Control-Shift-Home: speak "document <state> to cursor position".
  7379.         # Control-Shift-End:  speak "document <state> from cursor position".
  7380.         #
  7381.         # Control-a:          speak "entire document selected".
  7382.         #
  7383.         # where <state> is either "selected" or "unselected" depending
  7384.         # upon whether there are any text selections.
  7385.         #
  7386.         if isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent):
  7387.             eventStr = orca_state.lastNonModifierKeyEvent.event_string
  7388.             mods = orca_state.lastInputEvent.modifiers
  7389.         else:
  7390.             eventStr = None
  7391.             mods = 0
  7392.  
  7393.         isControlKey = mods & settings.CTRL_MODIFIER_MASK
  7394.         isShiftKey = mods & settings.SHIFT_MODIFIER_MASK
  7395.         selectedText = (text.getNSelections() != 0)
  7396.  
  7397.         specialCaseFound = False
  7398.         if (eventStr == "Page_Down") and isShiftKey and isControlKey:
  7399.             specialCaseFound = True
  7400.             # Translators: when the user selects (highlights) text in
  7401.             # a document, Orca will speak information about what they
  7402.             # have selected.
  7403.             #
  7404.             line = _("line selected to end from previous cursor position")
  7405.  
  7406.         elif (eventStr == "Page_Up") and isShiftKey and isControlKey:
  7407.             specialCaseFound = True
  7408.             # Translators: when the user selects (highlights) text in
  7409.             # a document, Orca will speak information about what they
  7410.             # have selected.
  7411.             #
  7412.             line = _("line selected from start to previous cursor position")
  7413.  
  7414.         elif (eventStr == "Page_Down") and isShiftKey and not isControlKey:
  7415.             specialCaseFound = True
  7416.             if selectedText:
  7417.                 # Translators: when the user selects (highlights) text in
  7418.                 # a document, Orca will speak information about what they
  7419.                 # have selected.
  7420.                 #
  7421.                 line = _("page selected from cursor position")
  7422.             else:
  7423.                 # Translators: when the user unselects text in a document,
  7424.                 # Orca will speak information about what they have unselected.
  7425.                 #
  7426.                 line = _("page unselected from cursor position")
  7427.  
  7428.         elif (eventStr == "Page_Up") and isShiftKey and not isControlKey:
  7429.             specialCaseFound = True
  7430.             if selectedText:
  7431.                 # Translators: when the user selects (highlights) text in
  7432.                 # a document, Orca will speak information about what they
  7433.                 # have selected.
  7434.                 #
  7435.                 line = _("page selected to cursor position")
  7436.             else:
  7437.                 # Translators: when the user unselects text in a document,
  7438.                 # Orca will speak information about what they have unselected.
  7439.                 #
  7440.                 line = _("page unselected to cursor position")
  7441.  
  7442.         elif (eventStr == "Down") and isShiftKey and isControlKey:
  7443.             specialCaseFound = True
  7444.             if selectedText:
  7445.                 # Translators: when the user selects (highlights) text in
  7446.                 # a document, Orca will speak information about what they
  7447.                 # have selected.
  7448.                 #
  7449.                 line = _("line selected down from cursor position")
  7450.             else:
  7451.                 # Translators: when the user unselects text in a document,
  7452.                 # Orca will speak information about what they have unselected.
  7453.                 #
  7454.                 line = _("line unselected down from cursor position")
  7455.  
  7456.         elif (eventStr == "Up") and isShiftKey and isControlKey:
  7457.             specialCaseFound = True
  7458.             if selectedText:
  7459.                 # Translators: when the user selects (highlights) text in
  7460.                 # a document, Orca will speak information about what they
  7461.                 # have selected.
  7462.                 #
  7463.                 line = _("line selected up from cursor position")
  7464.             else:
  7465.                 # Translators: when the user unselects text in a document,
  7466.                 # Orca will speak information about what they have unselected.
  7467.                 #
  7468.                 line = _("line unselected up from cursor position")
  7469.  
  7470.         elif (eventStr == "Home") and isShiftKey and isControlKey:
  7471.             specialCaseFound = True
  7472.             if selectedText:
  7473.                 # Translators: when the user selects (highlights) text in
  7474.                 # a document, Orca will speak information about what they
  7475.                 # have selected.
  7476.                 #
  7477.                 line = _("document selected to cursor position")
  7478.             else:
  7479.                 # Translators: when the user unselects text in a document,
  7480.                 # Orca will speak information about what they have unselected.
  7481.                 #
  7482.                 line = _("document unselected to cursor position")
  7483.  
  7484.         elif (eventStr == "End") and isShiftKey and isControlKey:
  7485.             specialCaseFound = True
  7486.             if selectedText:
  7487.                 # Translators: when the user selects (highlights) text in
  7488.                 # a document, Orca will speak information about what they
  7489.                 # have selected.
  7490.                 #
  7491.                 line = _("document selected from cursor position")
  7492.             else:
  7493.                 # Translators: when the user unselects text in a document,
  7494.                 # Orca will speak information about what they have unselected.
  7495.                 #
  7496.                 line = _("document unselected from cursor position")
  7497.  
  7498.         elif (eventStr == "A") and isControlKey:
  7499.             # The user has typed Control-A. Check to see if the entire
  7500.             # document has been selected, and if so, let the user know.
  7501.             #
  7502.             charCount = text.characterCount
  7503.             for i in range(0, text.getNSelections()):
  7504.                 [startOffset, endOffset] = text.getSelection(i)
  7505.                 if text.caretOffset == 0 and \
  7506.                    startOffset == 0 and endOffset == charCount:
  7507.                     specialCaseFound = True
  7508.                     self.updateBraille(obj)
  7509.  
  7510.                     # Translators: this means the user has selected
  7511.                     # all the text in a document (e.g., Ctrl+a in gedit).
  7512.                     #
  7513.                     line = _("entire document selected")
  7514.  
  7515.         if specialCaseFound:
  7516.             speech.speak(line, None, False)
  7517.             return
  7518.         elif startOffset == endOffset:
  7519.             return
  7520.  
  7521.         try:
  7522.             # If we are selecting by word, then there possibly will be
  7523.             # whitespace characters on either end of the text. We adjust
  7524.             # the startOffset and endOffset to exclude them.
  7525.             #
  7526.             try:
  7527.                 tmpStr = text.getText(startOffset,
  7528.                                       endOffset).decode("UTF-8")
  7529.             except:
  7530.                 tmpStr = u''
  7531.             n = len(tmpStr)
  7532.  
  7533.             # Don't strip whitespace if string length is one (might be a
  7534.             # space).
  7535.             #
  7536.             if n > 1:
  7537.                 while endOffset > startOffset:
  7538.                     if self.isWordDelimiter(tmpStr[n-1]):
  7539.                         n -= 1
  7540.                         endOffset -= 1
  7541.                     else:
  7542.                         break
  7543.                 n = 0
  7544.                 while startOffset < endOffset:
  7545.                     if self.isWordDelimiter(tmpStr[n]):
  7546.                         n += 1
  7547.                         startOffset += 1
  7548.                     else:
  7549.                         break
  7550.  
  7551.         except:
  7552.             debug.printException(debug.LEVEL_FINEST)
  7553.  
  7554.         if self.isTextSelected(obj, startOffset, endOffset):
  7555.             # Translators: when the user selects (highlights) text in
  7556.             # a document, Orca lets them know this.
  7557.             #
  7558.             speech.speak(C_("text", "selected"), None, False)
  7559.         elif len(text.getText(startOffset, endOffset)):
  7560.             # Translators: when the user unselects
  7561.             # (unhighlights) text in a document, Orca lets
  7562.             # them know this.
  7563.             #
  7564.             speech.speak(C_("text", "unselected"), None, False)
  7565.  
  7566.         self._saveLastTextSelections(text)
  7567.  
  7568.     def getURI(self, obj):
  7569.         """Return the URI for a given link object.
  7570.  
  7571.         Arguments:
  7572.         - obj: the Accessible object.
  7573.         """
  7574.         return obj.queryHyperlink().getURI(0)
  7575.  
  7576.     def getDocumentFrame(self):
  7577.         """Dummy method used as a reminder to refactor whereamI for links,
  7578.         possibly subclassing whereamI for the Gecko script.
  7579.         """
  7580.         return None
  7581.  
  7582.     def systemBeep(self):
  7583.         """Rings the system bell.  This is really a hack.  Ideally, we want
  7584.         a method that will present an earcon (any sound designated representing
  7585.         an error, event etc)
  7586.         """
  7587.         print "\a"
  7588.  
  7589.     def setCaretOffset(self, obj, offset):
  7590.         """Set the caret offset on a given accessible. Similar to
  7591.         Accessible.setCaretOffset()
  7592.  
  7593.         Arguments:
  7594.         - obj: Given accessible object.
  7595.         - offset: Offset to hich to set the caret.
  7596.         """
  7597.         try:
  7598.             texti = obj.queryText()
  7599.         except:
  7600.             return None
  7601.  
  7602.         texti.setCaretOffset(offset)
  7603.  
  7604.     def attribsToDictionary(self, dict_string):
  7605.         """Creates a Python dict from a typical attributes list returned from
  7606.         different AT-SPI methods.
  7607.  
  7608.         Arguments:
  7609.         - dict_string: A list of colon seperated key/value pairs seperated by
  7610.         semicolons.
  7611.         Returns a Python dict of the given attributes.
  7612.         """
  7613.         try:
  7614.             return dict(
  7615.                 map(lambda pair: pair.strip().split(':'),
  7616.                     dict_string.strip('; ').split(';')))
  7617.         except ValueError:
  7618.             return {}
  7619.  
  7620.     def _getPopupItemAtDesktopCoords(self, x, y):
  7621.         """Since pop-up items often don't contain nested components, we need
  7622.         a way to efficiently determine if the cursor is over a menu item.
  7623.  
  7624.         Arguments:
  7625.         - x: X coordinate.
  7626.         - y: Y coordinate.
  7627.  
  7628.         Returns a menu item the mouse is over, or None.
  7629.         """
  7630.         suspect_children = []
  7631.         if self.lastSelectedMenu:
  7632.             try:
  7633.                 si = self.lastSelectedMenu.querySelection()
  7634.             except NotImplementedError:
  7635.                 return None
  7636.  
  7637.             if si.nSelectedChildren > 0:
  7638.                 suspect_children = [si.getSelectedChild(0)]
  7639.             else:
  7640.                 suspect_children = self.lastSelectedMenu
  7641.             for child in suspect_children:
  7642.                 try:
  7643.                     ci = child.queryComponent()
  7644.                 except NotImplementedError:
  7645.                     continue
  7646.  
  7647.                 if ci.contains(x, y, pyatspi.DESKTOP_COORDS) \
  7648.                    and ci.getLayer() == pyatspi.LAYER_POPUP:
  7649.                     return child
  7650.  
  7651.     def getComponentAtDesktopCoords(self, parent, x, y):
  7652.         """Get the descendant component at the given desktop coordinates.
  7653.  
  7654.         Arguments:
  7655.  
  7656.         - parent: The parent component we are searching below.
  7657.         - x: X coordinate.
  7658.         - y: Y coordinate.
  7659.  
  7660.         Returns end-node that contains the given coordinates, or None.
  7661.         """
  7662.         acc = self._getPopupItemAtDesktopCoords(x, y)
  7663.         if acc:
  7664.             return acc
  7665.  
  7666.         container = parent
  7667.         while True:
  7668.             if container.getRole() == pyatspi.ROLE_PAGE_TAB_LIST:
  7669.                 try:
  7670.                     si = container.querySelection()
  7671.                     container = si.getSelectedChild(0)[0]
  7672.                 except NotImplementedError:
  7673.                     pass
  7674.             try:
  7675.                 ci = container.queryComponent()
  7676.             except:
  7677.                 return None
  7678.             else:
  7679.                 inner_container = container
  7680.             container =  ci.getAccessibleAtPoint(x, y, pyatspi.DESKTOP_COORDS)
  7681.             if not container or container.queryComponent() == ci:
  7682.                 # The gecko bridge simply has getAccessibleAtPoint return
  7683.                 # itself if there are no further children.
  7684.                 # TODO: Put in Gecko.py
  7685.                 break
  7686.         if inner_container == parent:
  7687.             return None
  7688.         else:
  7689.             return inner_container
  7690.  
  7691.     def getTextSelections(self, acc):
  7692.         """Get a list of text selections in the given accessible object,
  7693.         equivelent to getNSelections()*texti.getSelection()
  7694.  
  7695.         Arguments:
  7696.         - acc: An accessible.
  7697.  
  7698.         Returns list of start and end offsets for multiple selections, or an
  7699.         empty list if nothing is selected or if the accessible does not support
  7700.         the text interface.
  7701.         """
  7702.         rv = []
  7703.         try:
  7704.             texti = acc.queryText()
  7705.         except:
  7706.             return rv
  7707.  
  7708.         for i in xrange(texti.getNSelections()):
  7709.             rv.append(texti.getSelection(i))
  7710.  
  7711.         return rv
  7712.  
  7713.     def speakWordUnderMouse(self, acc):
  7714.         """Determine if the speak-word-under-mouse capability applies to
  7715.         the given accessible.
  7716.  
  7717.         Arguments:
  7718.         - acc: Accessible to test.
  7719.  
  7720.         Returns True if this accessible should provide the single word.
  7721.         """
  7722.         return acc and acc.getState().contains(pyatspi.STATE_EDITABLE)
  7723.  
  7724.     def getTextAttributes(self, acc, offset, get_defaults=False):
  7725.         """Get the text attributes run for a given offset in a given accessible
  7726.  
  7727.         Arguments:
  7728.         - acc: An accessible.
  7729.         - offset: Offset in the accessible's text for which to retrieve the
  7730.         attributes.
  7731.         - get_defaults: Get the default attributes as well as the unique ones.
  7732.         Default is True
  7733.  
  7734.         Returns a dictionary of attributes, a start offset where the attributes
  7735.         begin, and an end offset. Returns ({}, 0, 0) if the accessible does not
  7736.         supprt the text attribute.
  7737.         """
  7738.         rv = {}
  7739.         try:
  7740.             texti = acc.queryText()
  7741.         except:
  7742.             return rv, 0, 0
  7743.  
  7744.         if get_defaults:
  7745.             rv.update(self.attribsToDictionary(texti.getDefaultAttributes()))
  7746.  
  7747.         attrib_str, start, end = texti.getAttributes(offset)
  7748.  
  7749.         rv.update(self.attribsToDictionary(attrib_str))
  7750.  
  7751.         return rv, start, end
  7752.  
  7753.     def getWordAtCoords(self, acc, x, y):
  7754.         """Get the word at the given coords in the accessible.
  7755.  
  7756.         Arguments:
  7757.         - acc: Accessible that supports the Text interface.
  7758.         - x: X coordinate.
  7759.         - y: Y coordinate.
  7760.  
  7761.         Returns a tuple containing the word, start offset, and end offset.
  7762.         """
  7763.         try:
  7764.             ti = acc.queryText()
  7765.         except NotImplementedError:
  7766.             return '', 0, 0
  7767.  
  7768.         text_contents = ti.getText(0, -1)
  7769.         line_offsets = []
  7770.         start_offset = 0
  7771.         while True:
  7772.             try:
  7773.                 end_offset = text_contents.index('\n', start_offset)
  7774.             except ValueError:
  7775.                 line_offsets.append((start_offset, len(text_contents)))
  7776.                 break
  7777.             line_offsets.append((start_offset, end_offset))
  7778.             start_offset = end_offset + 1
  7779.         for start, end in line_offsets:
  7780.             bx, by, bw, bh = \
  7781.                 ti.getRangeExtents(start, end, pyatspi.DESKTOP_COORDS)
  7782.             bb = mouse_review.BoundingBox(bx, by, bw, bh)
  7783.             if bb.isInBox(x, y):
  7784.                 start_offset = 0
  7785.                 word_offsets = []
  7786.                 while True:
  7787.                     try:
  7788.                         end_offset = \
  7789.                             text_contents[start:end].index(' ', start_offset)
  7790.                     except ValueError:
  7791.                         word_offsets.append((start_offset,
  7792.                                              len(text_contents[start:end])))
  7793.                         break
  7794.                     word_offsets.append((start_offset, end_offset))
  7795.                     start_offset = end_offset + 1
  7796.                 for a, b in word_offsets:
  7797.                     bx, by, bw, bh = \
  7798.                         ti.getRangeExtents(start+a, start+b,
  7799.                                            pyatspi.DESKTOP_COORDS)
  7800.                     bb = mouse_review.BoundingBox(bx, by, bw, bh)
  7801.                     if bb.isInBox(x, y):
  7802.                         return text_contents[start+a:start+b], start+a, start+b
  7803.         return '', 0, 0
  7804.  
  7805. # Dictionary that defines the state changes we care about for various
  7806. # objects.  The key represents the role and the value represents a list
  7807. # of states that we care about.
  7808. #
  7809. state_change_notifiers = {}
  7810.  
  7811. state_change_notifiers[pyatspi.ROLE_CHECK_MENU_ITEM] = ("checked", None)
  7812. state_change_notifiers[pyatspi.ROLE_CHECK_BOX]       = ("checked",
  7813.                                                         "indeterminate",
  7814.                                                         None)
  7815. state_change_notifiers[pyatspi.ROLE_PANEL]           = ("showing", None)
  7816. state_change_notifiers[pyatspi.ROLE_LABEL]           = ("showing", None)
  7817. state_change_notifiers[pyatspi.ROLE_RADIO_BUTTON]    = ("checked", None)
  7818. state_change_notifiers[pyatspi.ROLE_TOGGLE_BUTTON]   = ("checked",
  7819.                                                         "pressed",
  7820.                                                         None)
  7821. state_change_notifiers[pyatspi.ROLE_TABLE_CELL]      = ("checked",
  7822.                                                         "expanded",
  7823.                                                         None)
  7824. state_change_notifiers[pyatspi.ROLE_LIST_ITEM]       = ("expanded", None)
  7825.